diff --git a/.github/workflows/backend-ci-cd.yml b/.github/workflows/backend-ci-cd.yml index 5d54b44f6..1fd67d308 100644 --- a/.github/workflows/backend-ci-cd.yml +++ b/.github/workflows/backend-ci-cd.yml @@ -41,10 +41,10 @@ jobs: with: node-version: "20" cache: "pnpm" - cache-dependency-path: "**/pnpm-lock.yaml" + cache-dependency-path: "backend/pnpm-lock.yaml" - name: Install dependencies - run: pnpm install --frozen-lockfile + run: cd backend && pnpm install --frozen-lockfile - name: Run unit tests run: cd backend && pnpm run test @@ -79,10 +79,10 @@ jobs: with: node-version: "20" cache: "pnpm" - cache-dependency-path: "**/pnpm-lock.yaml" + cache-dependency-path: "backend/pnpm-lock.yaml" - name: Install dependencies - run: pnpm install --frozen-lockfile + run: cd backend && pnpm install --frozen-lockfile - name: Build run: cd backend && pnpm run build diff --git a/.gitignore b/.gitignore index 092bd124d..6a57ba61a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,94 +1,21 @@ -# Dependencies -node_modules/ -**/node_modules/ -.pnp -.pnp.js - -# Build outputs -dist/ -build/ -**/dist/ -**/build/ -.next/ -out/ - # Rust's output directory -target/ -**/target/ - -# Testing -coverage/ -.nyc_output/ -*.log -test_snapshots/ -**/test_snapshots/ - -# Environment files -.env -.env.local -.env.*.local -*.env - -# IDEs and editors -.vscode/ -.idea/ -*.swp -*.swo -*~ -.DS_Store +target -# Soroban/Stellar -.soroban/ -.stellar/ +/node_modules -# Database -*.db -*.sqlite -*.sqlite3 +# Local settings +.soroban +.stellar -# Prisma -generated/prisma/ +# Test outputs +contracts/test_snapshots/ +PR_DESCRIPTION.md +PR_DESCRIPTION_ISSUE75.md +PR_DESCRIPTION_ISSUE74.md /generated/prisma -# Temporary files -*.tmp -*.temp -.cache/ -.temp/ - -# OS files -Thumbs.db -.DS_Store - -# Logs -logs/ -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -# Agent files .agent/ .agents/ issue.md -# Documentation drafts -PR_DESCRIPTION.md -PR_DESCRIPTION_ISSUE75.md -PR_DESCRIPTION_ISSUE74.md - -# Package manager locks (keep pnpm-lock.yaml) -# Uncomment if you want to ignore lock files -# package-lock.json -# yarn.lock - -# WASM build artifacts -*.wasm -!contracts/target/wasm32-unknown-unknown/release/*.wasm - -# Backup files -*.bak -*.backup -*~ \ No newline at end of file +/contracts/test_snapshots \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ed6eb21ef..5948a86d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { "kiroAgent.configureMCP": "Disabled" -} \ No newline at end of file +} diff --git a/GOALS_PR_DESCRIPTION.md b/GOALS_PR_DESCRIPTION.md index 4cc097e86..37319096a 100644 --- a/GOALS_PR_DESCRIPTION.md +++ b/GOALS_PR_DESCRIPTION.md @@ -154,717 +154,3 @@ curl -X POST http://localhost:3000/savings/goals \ - Date comparison is done at day-level (time component ignored) - Both DTO and service layers validate for defense-in-depth - Metadata field remains optional and flexible for frontend needs -# 🚀 Implementation Summary - Nestera Backend Security & Challenges System - -## 📋 Overview - -This document summarizes all implementations completed for the Nestera backend, including comprehensive authentication security enhancements and a complete rewards challenges system. - ---- - -## 🔐 Part 1: Authentication Security Enhancements - -### 🎯 Objectives - -1. **Fix Critical Nonce Security Vulnerability** - Implement proper Redis-backed nonce caching -2. **Implement Comprehensive Rate Limiting** - Prevent brute force and DDoS attacks -3. **Add Progressive Security Measures** - IP banning, account lockouts, and progressive delays - ---- - -### ✅ 1. Nonce Security Implementation - -#### Problem Fixed -- **Critical Vulnerability**: Nonce caching was completely bypassed with `const storedNonce = nonce;` -- **Impact**: Enabled replay attacks, no expiration, session hijacking possible - -#### Solution Implemented -- ✅ Redis-backed nonce storage with 5-minute TTL -- ✅ Atomic nonce consumption (get + verify + delete) -- ✅ Timestamp validation for additional security -- ✅ Rate limiting (5 nonce requests per 15 minutes per public key) -- ✅ Comprehensive logging and monitoring - -#### Files Modified/Created -``` -backend/src/auth/ -├── auth.service.ts # Updated with nonce caching -├── auth.service.spec.ts # 22 tests (all passing ✅) -├── NONCE_SECURITY.md # Complete technical documentation -└── QUICK_REFERENCE.md # Developer quick reference -``` - -#### Test Results -``` -✅ Test Suites: 1 passed -✅ Tests: 22 passed, 22 total -✅ Time: 5.798s -``` - -#### Security Improvements -| Feature | Before | After | -|---------|--------|-------| -| Nonce Storage | ❌ Bypassed | ✅ Redis with TTL | -| Replay Protection | ❌ None | ✅ Atomic consumption | -| Rate Limiting | ❌ None | ✅ 5 per 15 min | -| Expiration | ❌ Never | ✅ 5 minutes | -| Logging | ⚠️ Minimal | ✅ Comprehensive | - ---- - -### ✅ 2. Authentication Rate Limiting System - -#### Problem Fixed -- **Vulnerability**: No protection against brute force attacks, credential stuffing, or DDoS -- **Impact**: Unlimited authentication attempts, no IP tracking, no account protection - -#### Solution Implemented - -##### Strict Per-Endpoint Rate Limits -| Endpoint | Limit | Window | Purpose | -|----------|-------|--------|---------| -| `/auth/register` | 3 | 1 hour | Prevent mass registration | -| `/auth/login` | 5 | 15 minutes | Prevent credential stuffing | -| `/auth/nonce` | 10 | 15 minutes | Prevent nonce flooding | -| `/auth/verify-signature` | 5 | 15 minutes | Prevent signature brute force | -| `/auth/2fa/validate` | 5 | 15 minutes | Prevent 2FA bypass | - -##### Progressive Delays -| Attempt | Delay | Purpose | -|---------|-------|---------| -| 1st | 0s | Normal operation | -| 2nd | 2s | Slow down attacker | -| 3rd | 5s | Further deterrent | -| 4th+ | 30s | Strong deterrent | - -##### IP-Based Protection -- **Threshold**: 10 failed attempts -- **Ban Duration**: 1 hour -- **Tracking Window**: 15 minutes -- **Storage**: Redis with auto-expiration - -##### Account Lockout -- **Threshold**: 5 failed attempts -- **Lock Duration**: 1 hour -- **Severe Cases**: 10+ attempts require email verification -- **Admin Override**: Available - -#### Files Created -``` -backend/src/auth/ -├── services/ -│ ├── auth-rate-limit.service.ts # Core rate limiting logic (330 lines) -│ └── auth-rate-limit.service.spec.ts # 22 tests (all passing ✅) -├── guards/ -│ └── auth-rate-limit.guard.ts # Rate limit enforcement (90 lines) -├── decorators/ -│ └── auth-rate-limit.decorator.ts # Route-level config (20 lines) -├── controllers/ -│ └── auth-security-admin.controller.ts # Admin management (140 lines) -├── AUTH_RATE_LIMITING.md # Complete documentation (800+ lines) -└── RATE_LIMITING_QUICK_START.md # Quick reference (200+ lines) -``` - -#### Files Modified -``` -backend/src/auth/ -├── auth.service.ts # Integrated rate limiting -├── auth.controller.ts # Applied rate limits to endpoints -└── auth.module.ts # Registered new services/guards -``` - -#### Admin Endpoints Added -``` -GET /auth/admin/security/metrics -GET /auth/admin/security/ip/:ip/status -DELETE /auth/admin/security/ip/:ip/ban -GET /auth/admin/security/account/:identifier/status -DELETE /auth/admin/security/account/:identifier/lock -DELETE /auth/admin/security/failed-attempts/:identifier -``` - -#### Test Results -``` -✅ Test Suites: 1 passed -✅ Tests: 22 passed, 22 total -✅ Time: 5.798s -✅ Build: SUCCESS -``` - -#### Security Improvements -| Metric | Before | After | -|--------|--------|-------| -| Brute Force Protection | ❌ None | ✅ Multi-layer | -| Rate Limiting | ⚠️ Global only | ✅ Per-endpoint | -| IP Tracking | ❌ None | ✅ Full tracking | -| Account Protection | ❌ None | ✅ Auto-lockout | -| Progressive Delays | ❌ None | ✅ 4 levels | -| Admin Tools | ❌ None | ✅ Full suite | -| Monitoring | ⚠️ Basic | ✅ Comprehensive | - ---- - -## 🎮 Part 2: Rewards Challenges System - -### 🎯 Objectives - -1. **Create Time-Bound Challenge System** - Allow users to discover and join challenges -2. **Implement Multiple Challenge Types** - Support various challenge mechanics -3. **Track User Participation** - Monitor progress and completion -4. **Provide Admin Management** - Full CRUD operations for challenges - ---- - -### ✅ Implementation Delivered - -#### Challenge Types Supported (5 types) - -1. **Deposit Streak** (`deposit_streak`) - - Track consecutive daily deposits - - Configurable streak days and minimum amount - - Progress: currentStreak, streakHistory - -2. **Goal Creation** (`goal_creation`) - - Track number of goals created - - Configurable goal count and minimum amount - - Progress: goalsCreated, goalIds - -3. **Referral** (`referral`) - - Track referrals made - - Option to require referral completion - - Progress: referralsCount, completedReferrals - -4. **Savings Target** (`savings_target`) - - Track total savings amount - - Configurable target amount - - Progress: currentAmount, deposits - -5. **Transaction Count** (`transaction_count`) - - Track number of transactions - - Filter by transaction type - - Progress: transactionCount, transactionIds - -#### API Endpoints Implemented - -##### Public/Authenticated Endpoints -``` -GET /rewards/challenges/active # List active challenges -GET /rewards/challenges/:id # Get challenge details -POST /rewards/challenges/:id/join # Join a challenge ✅ -GET /rewards/challenges/my/active # Get my active challenges -GET /rewards/challenges/my/all # Get all my challenges -``` - -##### Admin Endpoints -``` -POST /rewards/challenges/admin/create # Create challenge -PUT /rewards/challenges/admin/:id # Update challenge -DELETE /rewards/challenges/admin/:id # Delete challenge -POST /rewards/challenges/admin/activate-scheduled # Activate scheduled -POST /rewards/challenges/admin/complete-expired # Complete expired -``` - -#### Files Created - -##### Entities (2 files) -``` -backend/src/modules/challenges/entities/ -├── challenge.entity.ts # Main challenge entity (180 lines) -│ ├── ChallengeType enum (5 types) -│ ├── ChallengeStatus enum (5 statuses) -│ ├── RewardConfiguration interface -│ └── ChallengeRules interface -└── user-challenge.entity.ts # User participation (100 lines) - ├── UserChallengeStatus enum (4 statuses) - └── ProgressMetadata interface -``` - -##### Services (1 file) -``` -backend/src/modules/challenges/services/ -└── rewards-challenges.service.ts # Core business logic (450 lines) - ├── getActiveChallenges() - ├── getChallengeById() - ├── joinChallenge() - ├── getUserChallenges() - ├── createChallenge() - ├── updateChallenge() - ├── deleteChallenge() - ├── activateScheduledChallenges() - └── completeExpiredChallenges() -``` - -##### Controllers (1 file) -``` -backend/src/modules/challenges/controllers/ -└── rewards-challenges.controller.ts # API endpoints (250 lines) - ├── Public endpoints (2) - ├── Authenticated endpoints (3) - └── Admin endpoints (5) -``` - -##### DTOs (1 file) -``` -backend/src/modules/challenges/dto/ -└── challenge.dto.ts # Request/response DTOs (250 lines) - ├── CreateChallengeDto - ├── UpdateChallengeDto - ├── JoinChallengeDto - ├── GetActiveChallengesQueryDto - ├── ChallengeResponseDto - └── UserChallengeResponseDto -``` - -##### Migrations (1 file) -``` -backend/src/migrations/ -└── 1714046400000-CreateChallengesSystem.ts # Database schema (200 lines) - ├── challenges table - ├── user_challenges table - └── All necessary indexes -``` - -##### Documentation (3 files) -``` -backend/src/modules/challenges/ -├── REWARDS_CHALLENGES_SYSTEM.md # Complete docs (800+ lines) -├── QUICK_START.md # Quick start guide (400+ lines) -└── backend/REWARDS_CHALLENGES_IMPLEMENTATION.md # Summary -``` - -##### Module Updates (1 file) -``` -backend/src/modules/challenges/ -└── challenges.module.ts # Updated with new entities/services -``` - -#### Database Schema - -##### challenges Table -```sql -CREATE TABLE challenges ( - id UUID PRIMARY KEY, - name VARCHAR(255), - description TEXT, - type ENUM('deposit_streak', 'goal_creation', 'referral', 'savings_target', 'transaction_count'), - status ENUM('draft', 'scheduled', 'active', 'completed', 'cancelled'), - startDate TIMESTAMP, - endDate TIMESTAMP, - rewardConfiguration JSONB, - rules JSONB, - participantCount INT DEFAULT 0, - completionCount INT DEFAULT 0, - isFeatured BOOLEAN DEFAULT false, - tags TEXT[], - -- ... additional fields -); - --- Indexes -CREATE INDEX idx_challenges_type_status ON challenges(type, status); -CREATE INDEX idx_challenges_dates ON challenges(startDate, endDate); -``` - -##### user_challenges Table -```sql -CREATE TABLE user_challenges ( - id UUID PRIMARY KEY, - userId UUID, - challengeId UUID, - status ENUM('active', 'completed', 'failed', 'expired'), - progressPercentage DECIMAL(10,2) DEFAULT 0, - progressMetadata JSONB DEFAULT '{}', - completedAt TIMESTAMP, - rewardClaimed BOOLEAN DEFAULT false, - joinedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - -- ... additional fields - UNIQUE(userId, challengeId) -); - --- Indexes -CREATE UNIQUE INDEX idx_user_challenge ON user_challenges(userId, challengeId); -CREATE INDEX idx_user_status ON user_challenges(userId, status); -``` - -#### Response Example (As Specified) - -```json -{ - "challenges": [ - { - "id": "ch_1", - "name": "7-Day Savings Streak", - "type": "deposit_streak", - "description": "Make a deposit every day for 7 consecutive days", - "startDate": "2026-04-25T00:00:00Z", - "endDate": "2026-05-01T00:00:00Z", - "status": "active", - "rewardConfiguration": { - "type": "badge", - "value": "Streak Master", - "metadata": { - "points": 100 - } - }, - "rules": { - "requiredStreakDays": 7, - "minimumDepositAmount": 10 - }, - "participantCount": 150, - "completionCount": 45, - "isFeatured": true, - "tags": ["streak", "deposit", "beginner"], - "userParticipation": { - "joined": false - } - } - ], - "total": 1 -} -``` - -#### Security Features -- ✅ JWT authentication for user endpoints -- ✅ Role-based access control (ADMIN role required) -- ✅ Input validation with class-validator -- ✅ Duplicate join prevention -- ✅ KYC requirement checking -- ✅ Account age verification -- ✅ Max participants enforcement -- ✅ Excluded users checking - -#### Challenge Lifecycle -``` -1. Creation (Admin) → Draft -2. Scheduling → Scheduled -3. Activation (auto/manual) → Active -4. User Participation → Active -5. Progress Tracking → In Progress -6. Completion/Expiration → Completed/Expired -``` - ---- - -## 📊 Overall Statistics - -### Files Created/Modified - -#### Authentication Security -- **Created**: 7 new files (1,800+ lines) -- **Modified**: 3 files -- **Tests**: 44 tests (all passing ✅) -- **Documentation**: 1,400+ lines - -#### Rewards Challenges -- **Created**: 11 new files (2,500+ lines) -- **Modified**: 1 file -- **Documentation**: 1,600+ lines - -### Total Impact -``` -📁 Files Created: 18 -📝 Files Modified: 4 -📄 Lines of Code: 4,300+ -📚 Documentation: 3,000+ -✅ Tests: 44 (100% passing) -🔒 Security Vulnerabilities Fixed: 2 critical -🎮 Challenge Types: 5 -🔌 API Endpoints: 16 new -``` - ---- - -## 🧪 Testing & Verification - -### Test Coverage -``` -Authentication Security: -✅ Nonce Security: 22/22 tests passing -✅ Rate Limiting: 22/22 tests passing -✅ Build: SUCCESS -✅ TypeScript: No errors - -Rewards Challenges: -✅ Build: SUCCESS -✅ TypeScript: No errors -✅ All endpoints documented -✅ Comprehensive validation -``` - -### Build Verification -```bash -✅ npm run build - SUCCESS -✅ No TypeScript errors -✅ No linting errors -✅ All modules properly configured -``` - ---- - -## 📚 Documentation - -### Authentication Security -1. **NONCE_SECURITY.md** (800+ lines) - - Technical implementation details - - Attack prevention strategies - - Configuration guide - - API flow documentation - -2. **AUTH_RATE_LIMITING.md** (800+ lines) - - Complete technical documentation - - API reference with examples - - Monitoring recommendations - - Troubleshooting guide - -3. **QUICK_REFERENCE.md** (200+ lines) - - Developer quick reference - - Common operations - - Testing examples - -4. **RATE_LIMITING_QUICK_START.md** (200+ lines) - - Quick start guide - - Configuration examples - - Admin operations - -### Rewards Challenges -1. **REWARDS_CHALLENGES_SYSTEM.md** (800+ lines) - - Complete technical documentation - - API reference with examples - - Data models - - Challenge lifecycle - - Best practices - -2. **QUICK_START.md** (400+ lines) - - Quick setup guide - - Example API calls - - Challenge type examples - - Testing workflow - -3. **REWARDS_CHALLENGES_IMPLEMENTATION.md** (600+ lines) - - Implementation summary - - Deployment guide - - Database schema - - Security features - ---- - -## 🚀 Deployment Guide - -### Prerequisites -```bash -# Ensure Redis is running -redis-cli ping # Should return PONG - -# Verify environment variables -REDIS_URL=redis://localhost:6379 -``` - -### Step 1: Run Migrations -```bash -npm run migration:run -``` - -### Step 2: Verify Build -```bash -npm run build -``` - -### Step 3: Start Application -```bash -npm run start:prod -``` - -### Step 4: Verify Endpoints -```bash -# Test authentication security -curl http://localhost:3000/auth/nonce?publicKey=GXXXXXXXX - -# Test challenges system -curl http://localhost:3000/rewards/challenges/active - -# Check API documentation -open http://localhost:3000/api/docs -``` - ---- - -## 🔐 Security Compliance - -### Standards Met -- ✅ **OWASP Top 10**: Prevents broken authentication (A07:2021) -- ✅ **PCI DSS**: Account lockout requirements (8.1.6, 8.1.7) -- ✅ **NIST 800-63B**: Authentication security guidelines -- ✅ **SOC 2**: Access control and monitoring requirements -- ✅ **GDPR**: Secure user authentication and audit trails - -### Security Features Implemented -- ✅ Multi-layer defense (rate limiting + IP banning + account lockout) -- ✅ Automatic expiration (Redis TTL) -- ✅ Admin override capabilities -- ✅ Comprehensive logging and monitoring -- ✅ HTTP security headers -- ✅ Input validation and sanitization -- ✅ Role-based access control - ---- - -## 📈 Performance Impact - -### Authentication Security -- **Successful auth**: 2 Redis operations, < 10ms overhead -- **Failed auth**: 4 Redis operations, < 20ms overhead -- **Blocked request**: 2 Redis operations, < 10ms overhead -- **Progressive delays**: Intentional (0-30 seconds) - -### Rewards Challenges -- **List challenges**: 1 database query, < 50ms -- **Join challenge**: 3 database queries, < 100ms -- **Get user challenges**: 2 database queries, < 75ms - -### Scalability -- ✅ Stateless services (horizontal scaling) -- ✅ Redis-backed caching (> 100,000 ops/sec) -- ✅ Indexed database queries -- ✅ JSONB for flexible metadata - ---- - -## 🔮 Future Enhancements - -### Authentication Security -- [ ] CAPTCHA integration after 3 failed attempts -- [ ] Email notifications for security events -- [ ] Geolocation tracking for unusual logins -- [ ] Device fingerprinting -- [ ] Anomaly detection with ML - -### Rewards Challenges -- [ ] Progress tracking services for each challenge type -- [ ] Automatic progress updates via event listeners -- [ ] Reward claiming mechanism -- [ ] Challenge completion notifications -- [ ] Leaderboards with prizes -- [ ] Team challenges -- [ ] Recurring challenges -- [ ] Challenge templates -- [ ] A/B testing -- [ ] Analytics dashboard - ---- - -## 🎯 Key Achievements - -### Security -✅ **Fixed 2 critical vulnerabilities** -- Nonce replay attack vulnerability -- Unlimited authentication attempts - -✅ **Implemented enterprise-grade security** -- Multi-layer defense system -- Comprehensive monitoring -- Admin management tools - -### Features -✅ **Built complete challenges system** -- 5 challenge types -- 16 new API endpoints -- Full CRUD operations -- User participation tracking - -✅ **Production-ready code** -- 100% test coverage for security features -- Comprehensive documentation -- Type-safe implementation -- Proper error handling - ---- - -## 📞 Support & Resources - -### Documentation -- **Authentication Security**: `backend/src/auth/` -- **Rewards Challenges**: `backend/src/modules/challenges/` -- **API Documentation**: `http://localhost:3000/api/docs` - -### Testing -```bash -# Run all tests -npm test - -# Run specific test suites -npm test -- auth.service.spec.ts -npm test -- auth-rate-limit.service.spec.ts - -# Build verification -npm run build -``` - -### Monitoring -```bash -# Watch authentication logs -tail -f logs/app.log | grep "auth" - -# Watch challenge events -tail -f logs/app.log | grep "challenge" - -# Check Redis -redis-cli -> KEYS auth:* -> KEYS challenge:* -``` - ---- - -## ✅ Verification Checklist - -### Pre-Deployment -- [x] All tests passing (44/44) -- [x] TypeScript compilation successful -- [x] No linting errors -- [x] Documentation complete -- [ ] Redis connection verified -- [ ] Environment variables set -- [ ] Staging deployment tested - -### Post-Deployment -- [ ] Rate limits enforced -- [ ] Progressive delays working -- [ ] IP bans functional -- [ ] Account lockouts functional -- [ ] Challenges system operational -- [ ] Admin endpoints accessible -- [ ] Logs being collected -- [ ] Monitoring configured - ---- - -## 🎉 Summary - -Successfully implemented comprehensive security enhancements and a complete rewards challenges system for the Nestera backend: - -### Authentication Security -- **2 critical vulnerabilities fixed** -- **44 tests passing** (100% coverage) -- **Multi-layer defense** (rate limiting + IP banning + account lockout) -- **Production-ready** with comprehensive monitoring - -### Rewards Challenges -- **5 challenge types** implemented -- **16 new API endpoints** created -- **Complete CRUD operations** for challenges -- **User participation tracking** system -- **Production-ready** with full documentation - -### Overall Impact -- **18 new files** created -- **4,300+ lines** of production code -- **3,000+ lines** of documentation -- **Zero security vulnerabilities** remaining -- **100% test coverage** for critical features - ---- - -**Status**: ✅ Production Ready -**Version**: 1.0.0 -**Date**: 2026-04-25 -**Team**: Backend Development -**Reviewed**: Security Team ✅ diff --git a/README.md b/README.md index 05ddb3567..a5c10be20 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ The project solves the problem of opaque, centralized savings platforms in emerg ## 🏗 Architecture Overview -- **Frontend (`frontend/`)** +- **Frontend (`apps/web`)** Next.js application for interacting with Nestera smart contracts. Provides user interface for creating savings accounts, depositing funds, and tracking progress. -- **Backend (`backend/`)** - NestJS API for off-chain services such as indexing contract events, sending notifications, managing user metadata, and aggregating analytics. +- **Backend (`apps/api`)** + Node.js API for off-chain services such as indexing contract events, sending notifications, managing user metadata, and aggregating analytics. - **Smart Contracts (`contracts/`)** Soroban smart contracts written in Rust that manage all savings logic, fund custody, interest calculations, and withdrawal rules. @@ -34,9 +34,11 @@ The project solves the problem of opaque, centralized savings platforms in emerg ## 📁 Repository Structure ```text / -├── frontend/ # Next.js frontend application -├── backend/ # NestJS backend API +├── apps/ +│ ├── web/ # Next.js frontend +│ └── api/ # Node.js backend API ├── contracts/ # Soroban smart contracts (Rust) +├── packages/ # Shared utilities and types ├── scripts/ # Deployment and automation scripts ├── tests/ # Integration and E2E tests └── README.md @@ -67,25 +69,11 @@ Before you begin, ensure you have the following installed: --- ## 📦 1. Clone the Repository - -### Quick Clone (Recommended) -For faster cloning, use a shallow clone: -```bash -git clone --depth 1 https://github.com/your-org/nestera.git -cd nestera -``` - -This reduces clone time significantly by downloading only the latest commit. - -### Full Clone (For Contributors) -If you need the full git history: ```bash git clone https://github.com/your-org/nestera.git cd nestera ``` -**Note:** We're working on reducing the repository size. See [CLONE_SPEED_FIX.md](CLONE_SPEED_FIX.md) for details. - --- ## 🔗 2. Smart Contracts Setup (Soroban) @@ -160,15 +148,15 @@ stellar contract invoke \ --- -## 🖥 3. Backend Setup (NestJS API) +## 🖥 3. Backend Setup (Node.js API) ```bash -cd backend -pnpm install +cd apps/api +npm install ``` ### Create Environment File -Create `.env` in `backend/`: +Create `.env` in `apps/api/`: ```env PORT=3001 NODE_ENV=development @@ -190,12 +178,12 @@ REDIS_URL=redis://localhost:6379 ### Run Database Migrations (if applicable) ```bash -pnpm run typeorm migration:run +npm run migrate ``` ### Start Backend Server ```bash -pnpm run start:dev +npm run dev ``` Backend should now be running at `http://localhost:3001` @@ -209,13 +197,13 @@ curl http://localhost:3001/health ## 🌐 4. Frontend Setup (Next.js) ```bash -cd frontend -pnpm install +cd apps/web +npm install ``` ### Create Environment File -Create `.env.local` in `frontend/`: +Create `.env.local` in `apps/web/`: ```env # Stellar Network NEXT_PUBLIC_STELLAR_NETWORK=testnet @@ -234,15 +222,15 @@ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your_project_id ### Run Development Server ```bash -pnpm dev +npm run dev ``` Frontend should now be running at `http://localhost:3000` ### Build for Production ```bash -pnpm build -pnpm start +npm run build +npm start ``` --- @@ -257,31 +245,31 @@ cargo test ### Backend Tests ```bash -cd backend -pnpm test +cd apps/api +npm test ``` Run with coverage: ```bash -pnpm run test:cov +npm run test:coverage ``` ### Frontend Tests ```bash -cd frontend -pnpm test +cd apps/web +npm test ``` Run E2E tests (requires running backend and deployed contracts): ```bash -pnpm run test:e2e +npm run test:e2e ``` ### Integration Tests From project root: ```bash -pnpm run test:integration +npm run test:integration ``` --- diff --git a/backend/.env.example b/backend/.env.example index 446c00bbd..fcfea417e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -35,7 +35,6 @@ DATABASE_URL=postgresql://user:password@localhost:5432/nestera # JWT JWT_SECRET=your_super_secret_key_here JWT_EXPIRATION=1h -JWT_REFRESH_EXPIRATION=7d # Redis (optional) REDIS_URL=redis://localhost:6379 diff --git a/backend/package.json b/backend/package.json index 78a760dff..373014c02 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,10 +18,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.js", - "check:migrations:down": "node ./scripts/check-migrations-down.js", - "test:rollback": "bash ./scripts/test-rollback.sh", - "generate:openapi": "ts-node -r tsconfig-paths/register scripts/generate-openapi-spec.ts" + "test:e2e": "jest --config ./test/jest-e2e.js" }, "dependencies": { "@aws-sdk/client-s3": "^3.1019.0", @@ -50,26 +47,19 @@ "cacheable": "^2.3.4", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", - "compression": "^1.8.1", "dotenv": "^17.3.1", "helmet": "^8.1.0", - "isomorphic-dompurify": "^3.10.0", "joi": "^18.0.2", "multer": "^2.1.1", "nestjs-pino": "^4.6.1", "nodemailer": "^8.0.1", "passport": "^0.7.0", - "passport-github2": "0.1.12", - "passport-google-oauth20": "2.0.0", "passport-jwt": "^4.0.1", - "passport-twitter": "1.0.4", "pdfkit": "^0.13.0", "pg": "^8.18.0", "pino-http": "^11.0.0", - "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "speakeasy": "^2.0.0", "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.28", "uuid": "^13.0.0" @@ -91,7 +81,6 @@ "@types/node": "^22.10.7", "@types/nodemailer": "^7.0.11", "@types/passport-jwt": "^4.0.1", - "@types/qrcode": "1.5.5", "@types/superagent": "^8.1.9", "@types/supertest": "^6.0.2", "@types/uuid": "^11.0.0", diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml new file mode 100644 index 000000000..ef622e674 --- /dev/null +++ b/backend/pnpm-lock.yaml @@ -0,0 +1,11815 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@aws-sdk/client-s3': + specifier: ^3.1019.0 + version: 3.1019.0 + '@keyv/redis': + specifier: ^5.1.6 + version: 5.1.6(keyv@5.6.0) + '@nestjs-modules/mailer': + specifier: ^2.0.2 + version: 2.0.2(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(nodemailer@8.0.1) + '@nestjs/axios': + specifier: ^4.0.1 + version: 4.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.5)(rxjs@7.8.2) + '@nestjs/cache-manager': + specifier: ^3.1.0 + version: 3.1.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) + '@nestjs/common': + specifier: ^11.0.1 + version: 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^4.0.3 + version: 4.0.3(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^11.0.1 + version: 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/event-emitter': + specifier: ^3.0.1 + version: 3.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14) + '@nestjs/jwt': + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/passport': + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) + '@nestjs/platform-express': + specifier: ^11.0.1 + version: 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14) + '@nestjs/schedule': + specifier: ^6.1.1 + version: 6.1.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14) + '@nestjs/swagger': + specifier: ^11.2.6 + version: 11.2.6(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^11.1.1 + version: 11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.5)(rxjs@7.8.2))(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))) + '@nestjs/throttler': + specifier: ^6.5.0 + version: 6.5.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2) + '@nestjs/typeorm': + specifier: ^11.0.0 + version: 11.0.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))) + '@stellar/stellar-sdk': + specifier: ^14.5.0 + version: 14.5.0 + axios: + specifier: ^1.13.5 + version: 1.13.5 + bcrypt: + specifier: ^6.0.0 + version: 6.0.0 + cache-manager: + specifier: ^7.2.8 + version: 7.2.8 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.3 + version: 0.14.3 + dotenv: + specifier: ^17.3.1 + version: 17.3.1 + helmet: + specifier: ^8.1.0 + version: 8.1.0 + joi: + specifier: ^18.0.2 + version: 18.0.2 + nestjs-pino: + specifier: ^4.6.1 + version: 4.6.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2) + nodemailer: + specifier: ^8.0.1 + version: 8.0.1 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + pg: + specifier: ^8.18.0 + version: 8.18.0 + pino-http: + specifier: ^11.0.0 + version: 11.0.0 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.1 + version: 7.8.2 + swagger-ui-express: + specifier: ^5.0.1 + version: 5.0.1(express@5.2.1) + typeorm: + specifier: ^0.3.28 + version: 0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + uuid: + specifier: ^13.0.0 + version: 13.0.0 + devDependencies: + '@eslint/eslintrc': + specifier: ^3.2.0 + version: 3.3.4 + '@eslint/js': + specifier: ^9.18.0 + version: 9.39.3 + '@fast-csv/format': + specifier: ^5.0.0 + version: 5.0.5 + '@nestjs/cli': + specifier: ^11.0.0 + version: 11.0.16(@swc/cli@0.6.0(@swc/core@1.15.13)(chokidar@4.0.3))(@swc/core@1.15.13)(@types/node@22.19.11) + '@nestjs/schematics': + specifier: ^11.0.0 + version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) + '@nestjs/testing': + specifier: ^11.0.1 + version: 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(@nestjs/platform-express@11.1.14) + '@swc/cli': + specifier: ^0.6.0 + version: 0.6.0(@swc/core@1.15.13)(chokidar@4.0.3) + '@swc/core': + specifier: ^1.10.7 + version: 1.15.13 + '@types/bcrypt': + specifier: ^6.0.0 + version: 6.0.0 + '@types/express': + specifier: ^5.0.0 + version: 5.0.6 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/multer': + specifier: ^2.0.0 + version: 2.0.0 + '@types/node': + specifier: ^22.10.7 + version: 22.19.11 + '@types/nodemailer': + specifier: ^7.0.11 + version: 7.0.11 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 + '@types/superagent': + specifier: ^8.1.9 + version: 8.1.9 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.3 + '@types/uuid': + specifier: ^11.0.0 + version: 11.0.0 + '@types/validator': + specifier: ^13.15.10 + version: 13.15.10 + eslint: + specifier: ^9.39.3 + version: 9.39.3(jiti@2.6.1) + eslint-config-prettier: + specifier: ^10.0.1 + version: 10.1.8(eslint@9.39.3(jiti@2.6.1)) + eslint-plugin-prettier: + specifier: ^5.2.2 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1))(prettier@3.8.1) + fast-check: + specifier: ^4.6.0 + version: 4.6.0 + globals: + specifier: ^16.0.0 + version: 16.5.0 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 + prettier: + specifier: ^3.8.1 + version: 3.8.1 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.2.2 + ts-jest: + specifier: ^29.2.5 + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3) + ts-loader: + specifier: ^9.5.2 + version: 9.5.4(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.13)) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.20.0 + version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + +packages: + + '@angular-devkit/core@19.2.17': + resolution: {integrity: sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/core@19.2.19': + resolution: {integrity: sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@19.2.19': + resolution: {integrity: sha512-7q9UY6HK6sccL9F3cqGRUwKhM7b/XfD2YcVaZ2WD7VMaRlRm85v6mRjSrfKIAwxcQU0UK27kMc79NIIqaHjzxA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@19.2.17': + resolution: {integrity: sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular-devkit/schematics@19.2.19': + resolution: {integrity: sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.1019.0': + resolution: {integrity: sha512-0pb9x7PPhS4oEi4c0rL3vzQQoXA4cWKtPuGga/UfVYLZ68yrqdq0NDKg0fr55qzdhNvWFCpmGx73g9Iyy03kkA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.25': + resolution: {integrity: sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.5': + resolution: {integrity: sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.23': + resolution: {integrity: sha512-EamaclJcCEaPHp6wiVknNMM2RlsPMjAHSsYSFLNENBM8Wz92QPc6cOn3dif6vPDQt0Oo4IEghDy3NMDCzY/IvA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.25': + resolution: {integrity: sha512-qPymamdPcLp6ugoVocG1y5r69ScNiRzb0hogX25/ij+Wz7c7WnsgjLTaz7+eB5BfRxeyUwuw5hgULMuwOGOpcw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.26': + resolution: {integrity: sha512-xKxEAMuP6GYx2y5GET+d3aGEroax3AgGfwBE65EQAUe090lzyJ/RzxPX9s8v7Z6qAk0XwfQl+LrmH05X7YvTeg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.26': + resolution: {integrity: sha512-EFcM8RM3TUxnZOfMJo++3PnyxFu1fL/huzmn3Vh+8IWRgqZawUD3cRwwOr+/4bE9DpyHaLOWFAjY0lfK5X9ZkQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.27': + resolution: {integrity: sha512-jXpxSolfFnPVj6GCTtx3xIdWNoDR7hYC/0SbetGZxOC9UnNmipHeX1k6spVstf7eWJrMhXNQEgXC0pD1r5tXIg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.23': + resolution: {integrity: sha512-IL/TFW59++b7MpHserjUblGrdP5UXy5Ekqqx1XQkERXBFJcZr74I7VaSrQT5dxdRMU16xGK4L0RQ5fQG1pMgnA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.26': + resolution: {integrity: sha512-c6ghvRb6gTlMznWhGxn/bpVCcp0HRaz4DobGVD9kI4vwHq186nU2xN/S7QGkm0lo0H2jQU8+dgpUFLxfTcwCOg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.26': + resolution: {integrity: sha512-cXcS3+XD3iwhoXkM44AmxjmbcKueoLCINr1e+IceMmCySda5ysNIfiGBGe9qn5EMiQ9Jd7pP0AGFtcd6OV3Lvg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + resolution: {integrity: sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.8': + resolution: {integrity: sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.5': + resolution: {integrity: sha512-SPSvF0G1t8m8CcB0L+ClNFszzQOvXaxmRj25oRWDf6aU+TuN2PXPFAJ9A6lt1IvX4oGAqqbTdMPTYs/SSHUYYQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.8': + resolution: {integrity: sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.9': + resolution: {integrity: sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.26': + resolution: {integrity: sha512-5q7UGSTtt7/KF0Os8wj2VZtlLxeWJVb0e2eDrDJlWot2EIxUNKDDMPFq/FowUqrwZ40rO2bu6BypxaKNvQhI+g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.8': + resolution: {integrity: sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.26': + resolution: {integrity: sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.16': + resolution: {integrity: sha512-L7Qzoj/qQU1cL5GnYLQP5LbI+wlLCLoINvcykR3htKcQ4tzrPf2DOs72x933BM7oArYj1SKrkb2lGlsJHIic3g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.10': + resolution: {integrity: sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.14': + resolution: {integrity: sha512-4nZSrBr1NO+48HCM/6BRU8mnRjuHZjcpziCvLXZk5QVftwWz5Mxqbhwdz4xf7WW88buaTB8uRO2MHklSX1m0vg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1019.0': + resolution: {integrity: sha512-OF+2RfRmUKyjzrRWlDcyju3RBsuqcrYDQ8TwrJg8efcOotMzuZN4U9mpVTIdATpmEc4lWNZBMSjPzrGm6JPnAQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + + '@aws-sdk/util-user-agent-node@3.973.12': + resolution: {integrity: sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.16': + resolution: {integrity: sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} + + '@cacheable/utils@2.3.4': + resolution: {integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@css-inline/css-inline-android-arm-eabi@0.14.1': + resolution: {integrity: sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@css-inline/css-inline-android-arm64@0.14.1': + resolution: {integrity: sha512-tH5us0NYGoTNBHOUHVV7j9KfJ4DtFOeTLA3cM0XNoMtArNu2pmaaBMFJPqECzavfXkLc7x5Z22UPZYjoyHfvCA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@css-inline/css-inline-darwin-arm64@0.14.1': + resolution: {integrity: sha512-QE5W1YRIfRayFrtrcK/wqEaxNaqLULPI0gZB4ArbFRd3d56IycvgBasDTHPre5qL2cXCO3VyPx+80XyHOaVkag==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@css-inline/css-inline-darwin-x64@0.14.1': + resolution: {integrity: sha512-mAvv2sN8awNFsbvBzlFkZPbCNZ6GCWY5/YcIz7V5dPYw+bHHRbjnlkNTEZq5BsDxErVrMIGvz05PGgzuNvZvdQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@css-inline/css-inline-linux-arm-gnueabihf@0.14.1': + resolution: {integrity: sha512-AWC44xL0X7BgKvrWEqfSqkT2tJA5kwSGrAGT+m0gt11wnTYySvQ6YpX0fTY9i3ppYGu4bEdXFjyK2uY1DTQMHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@css-inline/css-inline-linux-arm64-gnu@0.14.1': + resolution: {integrity: sha512-drj0ciiJgdP3xKXvNAt4W+FH4KKMs8vB5iKLJ3HcH07sNZj58Sx++2GxFRS1el3p+GFp9OoYA6dgouJsGEqt0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@css-inline/css-inline-linux-arm64-musl@0.14.1': + resolution: {integrity: sha512-FzknI+st8eA8YQSdEJU9ykcM0LZjjigBuynVF5/p7hiMm9OMP8aNhWbhZ8LKJpKbZrQsxSGS4g9Vnr6n6FiSdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@css-inline/css-inline-linux-x64-gnu@0.14.1': + resolution: {integrity: sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@css-inline/css-inline-linux-x64-musl@0.14.1': + resolution: {integrity: sha512-6CRAZzoy1dMLPC/tns2rTt1ZwPo0nL/jYBEIAsYTCWhfAnNnpoLKVh5Nm+fSU3OOwTTqU87UkGrFJhObD/wobQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@css-inline/css-inline-win32-x64-msvc@0.14.1': + resolution: {integrity: sha512-nzotGiaiuiQW78EzsiwsHZXbxEt6DiMUFcDJ6dhiliomXxnlaPyBfZb6/FMBgRJOf6sknDt/5695OttNmbMYzg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@css-inline/css-inline@0.14.1': + resolution: {integrity: sha512-u4eku+hnPqqHIGq/ZUQcaP0TrCbYeLIYBaK7qClNRGZbnh8RC4gVxLEIo8Pceo1nOK9E5G4Lxzlw5KnXcvflfA==} + engines: {node: '>= 10'} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.4': + resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.3': + resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fast-csv/format@5.0.5': + resolution: {integrity: sha512-0P9SJXXnqKdmuWlLaTelqbrfdgN37Mvrb369J6eNmqL41IEIZQmV4sNM4GgAK2Dz3aH04J0HKGDMJFkYObThTw==} + + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.6': + resolution: {integrity: sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@keyv/redis@5.1.6': + resolution: {integrity: sha512-eKvW6pspvVaU5dxigaIDZr635/Uw6urTXL3gNbY9WTR8d3QigZQT+r8gxYSEOsw4+1cCBsC4s7T2ptR0WC9LfQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + + '@napi-rs/nice-android-arm-eabi@1.1.1': + resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.1.1': + resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.1.1': + resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.1.1': + resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.1.1': + resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-musl@1.1.1': + resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-openharmony-arm64@1.1.1': + resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.1.1': + resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} + engines: {node: '>= 10'} + + '@nestjs-modules/mailer@2.0.2': + resolution: {integrity: sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==} + peerDependencies: + '@nestjs/common': '>=7.0.9' + '@nestjs/core': '>=7.0.9' + nodemailer: '>=6.4.6' + + '@nestjs/axios@4.0.1': + resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + axios: ^1.3.1 + rxjs: ^7.0.0 + + '@nestjs/cache-manager@3.1.0': + resolution: {integrity: sha512-pEIqYZrBcE8UdkJmZRduurvoUfdU+3kRPeO1R2muiMbZnRuqlki5klFFNllO9LyYWzrx98bd1j0PSPKSJk1Wbw==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 + cache-manager: '>=6' + keyv: '>=5' + rxjs: ^7.8.1 + + '@nestjs/cli@11.0.16': + resolution: {integrity: sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==} + engines: {node: '>= 20.11'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@11.1.14': + resolution: {integrity: sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==} + peerDependencies: + class-transformer: '>=0.4.1' + class-validator: '>=0.13.2' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/config@4.0.3': + resolution: {integrity: sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@11.1.14': + resolution: {integrity: sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==} + engines: {node: '>= 20'} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + '@nestjs/websockets': ^11.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/event-emitter@3.0.1': + resolution: {integrity: sha512-0Ln/x+7xkU6AJFOcQI9tIhUMXVF7D5itiaQGOyJbXtlAfAIt8gzDdJm+Im7cFzKoWkiW5nCXCPh6GSvdQd/3Dw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + + '@nestjs/jwt@11.0.2': + resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + + '@nestjs/mapped-types@2.1.0': + resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/passport@11.0.5': + resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 + + '@nestjs/platform-express@11.1.14': + resolution: {integrity: sha512-Fs+/j+mBSBSXErOQJ/YdUn/HqJGSJ4pGfiJyYOyz04l42uNVnqEakvu1kXLbxMabR6vd6/h9d6Bi4tso9p7o4Q==} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + + '@nestjs/schedule@6.1.1': + resolution: {integrity: sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + + '@nestjs/schematics@11.0.9': + resolution: {integrity: sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==} + peerDependencies: + typescript: '>=4.8.2' + + '@nestjs/swagger@11.2.6': + resolution: {integrity: sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==} + peerDependencies: + '@fastify/static': ^8.0.0 || ^9.0.0 + '@nestjs/common': ^11.0.1 + '@nestjs/core': ^11.0.1 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/terminus@11.1.1': + resolution: {integrity: sha512-Ssql79H+EQY/Wg108eJqN4NiNsO/tLrj+qbzOWSQUf2JE4vJQ2RG3WTqUOrYjfjWmVHD3+Ys0+azed7LSMKScw==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + + '@nestjs/testing@11.1.14': + resolution: {integrity: sha512-cQxX0ronsTbpfHz8/LYOVWXxoTxv6VoxrnuZoQaVX7QV2PSMqxWE7/9jSQR0GcqAFUEmFP34c6EJqfkjfX/k4Q==} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + + '@nestjs/throttler@6.5.0': + resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + + '@nestjs/typeorm@11.0.0': + resolution: {integrity: sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxt/opencollective@0.4.1': + resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} + engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} + hasBin: true + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/client@5.11.0': + resolution: {integrity: sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==} + engines: {node: '>= 18'} + peerDependencies: + '@node-rs/xxhash': ^1.1.0 + peerDependenciesMeta: + '@node-rs/xxhash': + optional: true + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@smithy/abort-controller@4.2.12': + resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.13': + resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.12': + resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.12': + resolution: {integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.12': + resolution: {integrity: sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.12': + resolution: {integrity: sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.12': + resolution: {integrity: sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.12': + resolution: {integrity: sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.13': + resolution: {integrity: sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.12': + resolution: {integrity: sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.12': + resolution: {integrity: sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.27': + resolution: {integrity: sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.44': + resolution: {integrity: sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.15': + resolution: {integrity: sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.0': + resolution: {integrity: sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.7': + resolution: {integrity: sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.43': + resolution: {integrity: sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.47': + resolution: {integrity: sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.12': + resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.20': + resolution: {integrity: sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.13': + resolution: {integrity: sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + + '@sqltools/formatter@1.2.5': + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@stellar/js-xdr@3.1.2': + resolution: {integrity: sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==} + + '@stellar/stellar-base@14.0.4': + resolution: {integrity: sha512-UbNW6zbdOBXJwLAV2mMak0bIC9nw3IZVlQXkv2w2dk1jgCbJjy3oRVC943zeGE5JAm0Z9PHxrIjmkpGhayY7kw==} + engines: {node: '>=20.0.0'} + + '@stellar/stellar-sdk@14.5.0': + resolution: {integrity: sha512-Uzjq+An/hUA+Q5ERAYPtT0+MMiwWnYYWMwozmZMjxjdL2MmSjucBDF8Q04db6K/ekU4B5cHuOfsdlrfaxQYblw==} + engines: {node: '>=20.0.0'} + hasBin: true + + '@swc/cli@0.6.0': + resolution: {integrity: sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==} + engines: {node: '>= 16.14.0'} + hasBin: true + peerDependencies: + '@swc/core': ^1.2.66 + chokidar: ^4.0.1 + peerDependenciesMeta: + chokidar: + optional: true + + '@swc/core-darwin-arm64@1.15.13': + resolution: {integrity: sha512-ztXusRuC5NV2w+a6pDhX13CGioMLq8CjX5P4XgVJ21ocqz9t19288Do0y8LklplDtwcEhYGTNdMbkmUT7+lDTg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.13': + resolution: {integrity: sha512-cVifxQUKhaE7qcO/y9Mq6PEhoyvN9tSLzCnnFZ4EIabFHBuLtDDO6a+vLveOy98hAs5Qu1+bb5Nv0oa1Pihe3Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.13': + resolution: {integrity: sha512-t+xxEzZ48enl/wGGy7SRYd7kImWQ/+wvVFD7g5JZo234g6/QnIgZ+YdfIyjHB+ZJI3F7a2IQHS7RNjxF29UkWw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.13': + resolution: {integrity: sha512-VndeGvKmTXFn6AGwjy0Kg8i7HccOCE7Jt/vmZwRxGtOfNZM1RLYRQ7MfDLo6T0h1Bq6eYzps3L5Ma4zBmjOnOg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.13': + resolution: {integrity: sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-x64-gnu@1.15.13': + resolution: {integrity: sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.13': + resolution: {integrity: sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.13': + resolution: {integrity: sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.13': + resolution: {integrity: sha512-hYXvyVVntqRlYoAIDwNzkS3tL2ijP3rxyWQMNKaxcCxxkCDto/w3meOK/OB6rbQSkNw0qTUcBfU9k+T0ptYdfQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.13': + resolution: {integrity: sha512-XTzKs7c/vYCcjmcwawnQvlHHNS1naJEAzcBckMI5OJlnrcgW8UtcX9NHFYvNjGtXuKv0/9KvqL4fuahdvlNGKw==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.13': + resolution: {integrity: sha512-0l1gl/72PErwUZuavcRpRAQN9uSst+Nk++niC5IX6lmMWpXoScYx3oq/narT64/sKv/eRiPTaAjBFGDEQiWJIw==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/bcrypt@6.0.0': + resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + + '@types/ejs@3.1.5': + resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + + '@types/mjml-core@4.15.2': + resolution: {integrity: sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==} + + '@types/mjml@4.7.4': + resolution: {integrity: sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/multer@2.0.0': + resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==} + + '@types/node@22.19.11': + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} + + '@types/nodemailer@7.0.11': + resolution: {integrity: sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==} + + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.17': + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + + '@types/pug@2.0.10': + resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@6.0.3': + resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + + '@types/uuid@11.0.0': + resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==} + deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed. + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xhmikosr/archive-type@7.1.0': + resolution: {integrity: sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==} + engines: {node: '>=18'} + + '@xhmikosr/bin-check@7.1.0': + resolution: {integrity: sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==} + engines: {node: '>=18'} + + '@xhmikosr/bin-wrapper@13.2.0': + resolution: {integrity: sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tar@8.1.0': + resolution: {integrity: sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tarbz2@8.1.0': + resolution: {integrity: sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-targz@8.1.0': + resolution: {integrity: sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==} + engines: {node: '>=18'} + + '@xhmikosr/decompress-unzip@7.1.0': + resolution: {integrity: sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==} + engines: {node: '>=18'} + + '@xhmikosr/decompress@10.2.0': + resolution: {integrity: sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==} + engines: {node: '>=18'} + + '@xhmikosr/downloader@15.2.0': + resolution: {integrity: sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==} + engines: {node: '>=18'} + + '@xhmikosr/os-filter-obj@3.0.0': + resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==} + engines: {node: ^14.14.0 || >=16.0.0} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + '@zone-eu/mailsplit@5.4.8': + resolution: {integrity: sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + alce@1.2.0: + resolution: {integrity: sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==} + engines: {node: '>=0.8.0'} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + arch@3.0.0: + resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + + b4a@1.8.0: + resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-walk@3.0.0-canary-5: + resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} + engines: {node: '>= 10.0.0'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + base32.js@0.1.0: + resolution: {integrity: sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==} + engines: {node: '>=0.12.0'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + bcrypt@6.0.0: + resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} + engines: {node: '>= 18'} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bin-version-check@5.1.0: + resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} + engines: {node: '>=12'} + + bin-version@6.0.0: + resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==} + engines: {node: '>=12'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cache-manager@7.2.8: + resolution: {integrity: sha512-0HDaDLBBY/maa/LmUVAr70XUOwsiQD+jyzCBjmUErYZUKdMS9dT59PqW59PpVqfGM7ve6H0J6307JTpkCYefHQ==} + + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@3.0.0: + resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001774: + resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + character-parser@2.2.0: + resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.3: + resolution: {integrity: sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==} + + clean-css@4.2.4: + resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==} + engines: {node: '>= 4.0'} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + comment-json@4.4.1: + resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==} + engines: {node: '>= 6'} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + constantinople@4.0.1: + resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron@4.4.0: + resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} + engines: {node: '>=18.x'} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + defaults@2.0.2: + resolution: {integrity: sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==} + engines: {node: '>=16'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + display-notification@2.0.0: + resolution: {integrity: sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==} + engines: {node: '>=4'} + + doctypes@1.1.0: + resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding-japanese@2.2.0: + resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} + engines: {node: '>=8.10.0'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-goat@3.0.0: + resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==} + engines: {node: '>=10'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-applescript@1.0.0: + resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==} + engines: {node: '>=0.10.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.3: + resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@1.2.5: + resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@1.9.3: + resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==} + engines: {node: '>=0.10.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + eventsource@2.0.2: + resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} + engines: {node: '>=12.0.0'} + + execa@0.10.0: + resolution: {integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==} + engines: {node: '>=4'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + ext-list@2.2.2: + resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} + engines: {node: '>=0.10.0'} + + ext-name@5.0.0: + resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} + engines: {node: '>=4'} + + extend-object@1.0.0: + resolution: {integrity: sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==} + + fast-check@4.6.0: + resolution: {integrity: sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA==} + engines: {node: '>=12.17.0'} + + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + hasBin: true + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + feaxios@0.0.23: + resolution: {integrity: sha512-eghR0A21fvbkcQBgZuMfQhrXxJzC0GNUGC9fXhBge33D+mFDTwl0aJ35zoQQn575BhyjQitRc5N4f+L4cP708g==} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + file-type@20.5.0: + resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} + engines: {node: '>=18'} + + file-type@21.3.0: + resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} + engines: {node: '>=20'} + + filelist@1.0.5: + resolution: {integrity: sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==} + engines: {node: 20 || >=22} + + filename-reserved-regex@3.0.0: + resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + filenamify@6.0.0: + resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==} + engines: {node: '>=16'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-versions@5.1.0: + resolution: {integrity: sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==} + engines: {node: '>=12'} + + fixpack@4.0.0: + resolution: {integrity: sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==} + hasBin: true + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.1.0: + resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} + engines: {node: '>=14.21.3'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hashery@1.5.0: + resolution: {integrity: sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==} + engines: {node: '>=20'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + helmet@8.1.0: + resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} + engines: {node: '>=18.0.0'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-minifier@4.0.0: + resolution: {integrity: sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==} + engines: {node: '>=6'} + hasBin: true + + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@5.0.1: + resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + inspect-with-kind@1.0.5: + resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-expression@4.0.0: + resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-retry-allowed@3.0.0: + resolution: {integrity: sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==} + engines: {node: '>=12'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + joi@18.0.2: + resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==} + engines: {node: '>= 20'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-stringify@1.0.2: + resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jstransformer@1.0.0: + resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + + juice@10.0.1: + resolution: {integrity: sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA==} + engines: {node: '>=10.0.0'} + hasBin: true + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libbase64@1.3.0: + resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} + + libmime@5.3.7: + resolution: {integrity: sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==} + + libphonenumber-js@1.12.37: + resolution: {integrity: sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==} + + libqp@2.1.1: + resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + liquidjs@10.24.0: + resolution: {integrity: sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==} + engines: {node: '>=16'} + hasBin: true + + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lower-case@1.1.4: + resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + mailparser@3.9.3: + resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + mensch@0.3.4: + resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + minimatch@10.2.2: + resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.3: + resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==} + + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.6: + resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mjml-accordion@4.18.0: + resolution: {integrity: sha512-9PUmy2JxIOGgAaVHvgVYX21nVAo3o/+wJckTTF/YTLGAqB+nm+44buxRzaXxVk7qXRwbCNfE8c8mlGVNh7vB1g==} + + mjml-body@4.18.0: + resolution: {integrity: sha512-34AwX70/7NkRIajPsa5j6NySRiNrlLatTKhiLwTVFiVtrEFlfCcbeMNmdVixI3Ldvs8209ZC6euaAnXDRyR1zw==} + + mjml-button@4.18.0: + resolution: {integrity: sha512-ZsWMI0j7EcFCMqbqdVwMWhmsVc03FhmypWXokKopGhwySn4IAB4AOURonRmFrO7k6sDeQ+iJ9QtTu7jA+S8wmg==} + + mjml-carousel@4.18.0: + resolution: {integrity: sha512-wY4g1CHCOoVSZuar7CLFon/qkPbICu71IT+6pa4BDwkAiaAMAemZPyy+a+iIUgdc8kHgSuHGsGf6PQzBSMWRZA==} + + mjml-cli@4.18.0: + resolution: {integrity: sha512-N6CnA4o/q/VRnGPxTzvVnjAEcF7WUVVQGYfS9SPAp0qwyf7RysMmewdS9yN8GwXwZV6L2sKdn+3ANNi2FNsJ7w==} + hasBin: true + + mjml-column@4.18.0: + resolution: {integrity: sha512-0QZ1whxbHUmJaRT8tW+wmr3fWZ/kpsHKAd24c7Z/N1Otm/U2G0T/FFEFJ6cB25X6ZN0K40QZ8L9gdLfiSVuRbA==} + + mjml-core@4.18.0: + resolution: {integrity: sha512-yey72LszXvIo5p0R6DB+YU8er/nP2wPsqpLKQCB0H8vG0WRT1sbSUvnCUOkKGn7subuyWDTdzHKbQO3XYIOmvg==} + + mjml-divider@4.18.0: + resolution: {integrity: sha512-FmGUVJqi4RYroh7y85vDx0aUKZgECkxHtMQ4pkLGQbZ2g93/Qt0Ek88DVCNJ5XwUAQQkE/TvrGMLHp3CIqpQ9Q==} + + mjml-group@4.18.0: + resolution: {integrity: sha512-28ABkXsKljBqj7XCC8GkQ94xz8HEU2XTyD+9LTlkDafzGp/MGJb8DcLh/7IkxCwqkQWyeMiDNLf1djsQ909Vxw==} + + mjml-head-attributes@4.18.0: + resolution: {integrity: sha512-nLzix1wrMnojE0RPGhk4iKqSRwHKjie2EPzgKT7CDzfqN+Ref03E5Q19x3cQTLgxvq3C3CnvCQBfnhoS3Eakug==} + + mjml-head-breakpoint@4.18.0: + resolution: {integrity: sha512-k6rwff+7i+vTQYJ/CjBfE20qNqPaW60IRH2x2oEPuCzmwDmoVWOcplJIuotSqIAdfwF9hLkICknisp1BpczVlQ==} + + mjml-head-font@4.18.0: + resolution: {integrity: sha512-ao8HB5nf+Dmxw4GO6lMMOlnj1lNZONai0GC9RobrZgPlghZw6hpURWGpkON7pQcy6XnOHwYwkV7Go/npzA2i7w==} + + mjml-head-html-attributes@4.18.0: + resolution: {integrity: sha512-xaQE1rthe0RrNotwEr71X1tE+QQ489Yc0ynMm3oNMrohDI/TaCeazx8GAHPMM7VLduDA8D4A5wkZ6PuEvlJu4w==} + + mjml-head-preview@4.18.0: + resolution: {integrity: sha512-2JvYqhbLyU/+Te6/1AXxzTNoHYCDYhXOVZP7wMvU4t7K34pXqyRUNO405atyHUY1MRafrl6RJ8cIx0x5vUX7PA==} + + mjml-head-style@4.18.0: + resolution: {integrity: sha512-nEwDHkAqY3Fm7QWeAZc/a7MakZpXh6THfrE8/AWrfpgzTHrD/wihNUc09ztNpr6z/K1+JWgQfSF2BRc+X3P46g==} + + mjml-head-title@4.18.0: + resolution: {integrity: sha512-0Hm8o50rPMUQLSCOOa4D4pz9NajmCDccLvBYE4fwKdeUXjSJ6bwAYeMpveel8oNZMDUVJ4Hx+PskisEGHMHM2w==} + + mjml-head@4.18.0: + resolution: {integrity: sha512-DS0adpIAsVMDIk2DOsHzjg+RNjQU0fF8jiVP9BmdRHVGrLPmpL9wIHZk2KvsKvZe7VaXXBijFt3DZ5/CQ/+D7Q==} + + mjml-hero@4.18.0: + resolution: {integrity: sha512-rujm0ROM4QGWw77vnl3NaVaCKXrT4xTSHeAnkHKiY5AuRf6HPTgEtutq5pdel/y6Q9GrmxvN3HRESum7tpJCJw==} + + mjml-image@4.18.0: + resolution: {integrity: sha512-e09NkoYwvzMcTv7V6H5doWD6Te2E1y2EvOLQJoXKVdQpDwyBWGdfnZke0scJGdA58HLAB+0mLYogpLwmfLaP5Q==} + + mjml-migrate@4.18.0: + resolution: {integrity: sha512-qfNCgW9zhJIsbPyXFA5RT/WY4mlje3N0WhHHOsHc0nY89Q01DenyslUy9nLLGXwi4K5FHS58oCjwWbMhwDcj1w==} + hasBin: true + + mjml-navbar@4.18.0: + resolution: {integrity: sha512-uho/MS2tfNAe+V9u2X7NoCco34MDbdp30ETA8009Qo1VCP/D8lZ+s69WGRPu6hvN/Y2pzBgZly++CMg3qFZqBQ==} + + mjml-parser-xml@4.18.0: + resolution: {integrity: sha512-sHSsZg4afY1heThuJzxa1Kvfh/QzB7/9P5fFUHeVnnxb07ZTXnhXWA6YbobdND5/l9+5yjN5/UgqDZm3tIT4Uw==} + + mjml-preset-core@4.18.0: + resolution: {integrity: sha512-x3l8vMVtsaqM/jauMeZIN7HFD2t5A28J4U0o4849yIlRxiWguLFV5l3BL8Byol+YLkoLuT9PjaZs9RYv+FGfeg==} + + mjml-raw@4.18.0: + resolution: {integrity: sha512-F/kViAwXm3ccPP52kw++/mHQbcYbYYxC8JH15TZxH8GLVZkX5CGKgcBrHhDK7WoIlfEIsVRZ6IZdlHjH8vgyxw==} + + mjml-section@4.18.0: + resolution: {integrity: sha512-bB8My9zvIEkTOxej+TrjEeaeRT0lsypGeRADtdrRZXeqUClkkuCnCXlsNKSLGT8ZRqjUqWRc5z8ubDOvGk2+Gg==} + + mjml-social@4.18.0: + resolution: {integrity: sha512-iAQc9g59L6L3VHDd55BxeIvk/zHkxflxmvuyYyOOvpmmKAvUBC//ULfpxiiM4yupofsThqFfrO+wc8d4kTRkbQ==} + + mjml-spacer@4.18.0: + resolution: {integrity: sha512-FK/0f5IBiONgaRpwNBs7G8EbLdAbmYqcIfHR8O8tP4LipAChLQKHO9vX3vrRMGLBZZNTESLObcFSVWmA40Mfpw==} + + mjml-table@4.18.0: + resolution: {integrity: sha512-vJysCPUL3CHcsQDAFpW+skzBtY0RYsmMBYswI4WX0B05GLKlOjXqpYOwcmAupWeGoBVL5r/t28ynu2PqnOlN3w==} + + mjml-text@4.18.0: + resolution: {integrity: sha512-hBLmF3JgveUKktKQFWHqHAr7qr92j1CxAvq7mtpDUgiWgyPFzqRX8mUsFYgZ7DmRxG4UE+Kzpt8/YFd9+E98lw==} + + mjml-validator@4.18.0: + resolution: {integrity: sha512-JmpWAsNTUlAxJOz2zHYfF8Vod8OzM3Qp5JXtrVw5tivZQzq88ZfqVGuqsas51z0pp1/ilfD4lC17YGfGwKGyhA==} + + mjml-wrapper@4.18.0: + resolution: {integrity: sha512-TZeOvLjIhXEK60rjWNiYhEYNlv5GKYahE+96ifcT5OGkWkRA0DsQDfp+6VI32OS5VxsfKq2h/UdERPlQijjpAQ==} + + mjml@4.18.0: + resolution: {integrity: sha512-rQM4aqFRrNvV1k733e8hJSopBjZvoSdBpRYzNTMAN+As0jqJsO5eN0wTT2IFtfe4PREzzu5b06RkPiUQdd0IIg==} + hasBin: true + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nestjs-pino@4.6.1: + resolution: {integrity: sha512-nuARXa0xpdJ1lY2+fgycIQr6H3g0VgqAWNK3xMYjOFcj2DoPETNXj0lV3Y86nRuI7BUfQp5PGiVoZvT4dTWbpQ==} + engines: {node: '>= 14'} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + pino: ^7.5.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + no-case@2.3.2: + resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + engines: {node: ^18 || ^20 || >= 21} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nodemailer@7.0.13: + resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==} + engines: {node: '>=6.0.0'} + + nodemailer@8.0.1: + resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==} + engines: {node: '>=6.0.0'} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@8.1.1: + resolution: {integrity: sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==} + engines: {node: '>=14.16'} + + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + + p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + param-case@2.1.1: + resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-expression-matcher@1.2.0: + resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + engines: {node: '>=14.0.0'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + + pg-connection-string@2.11.0: + resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.11.0: + resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.11.0: + resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.18.0: + resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-http@11.0.0: + resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + piscina@4.9.2: + resolution: {integrity: sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + preview-email@3.1.1: + resolution: {integrity: sha512-nrdhnt+E9ClJ4khk9rNzqgsxubH7xSJSKoqXx/7aed2eghegNGNWkSGOelNgFgUtMz3LmKGks0waH2NuXWWmPg==} + engines: {node: '>=14'} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pug-attrs@3.0.0: + resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} + + pug-code-gen@3.0.3: + resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==} + + pug-error@2.1.0: + resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} + + pug-filters@4.0.0: + resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==} + + pug-lexer@5.0.1: + resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==} + + pug-linker@4.0.0: + resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==} + + pug-load@3.0.0: + resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==} + + pug-parser@6.0.0: + resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==} + + pug-runtime@3.0.1: + resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==} + + pug-strip-comments@2.0.0: + resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==} + + pug-walk@2.0.0: + resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} + + pug@3.0.3: + resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + pure-rand@8.4.0: + resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@3.2.0: + resolution: {integrity: sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==} + engines: {node: '>=4'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + seek-bzip@2.0.0: + resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==} + hasBin: true + + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + + semver-regex@4.0.5: + resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} + engines: {node: '>=12'} + + semver-truncate@3.0.0: + resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==} + engines: {node: '>=12'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slick@1.12.2: + resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + sort-keys-length@1.0.1: + resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} + engines: {node: '>=0.10.0'} + + sort-keys@1.1.2: + resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + engines: {node: '>=14'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-dirs@3.0.0: + resolution: {integrity: sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==} + + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + strnum@2.2.2: + resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} + + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swagger-ui-dist@5.31.0: + resolution: {integrity: sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==} + + swagger-ui-dist@5.31.2: + resolution: {integrity: sha512-uIoesCjDcxnAKj/C/HG5pjHZMQs2K/qmqpUlwLxxaVryGKlgm8Ri+VOza5xywAqf//pgg/hW16RYa6dDuTCOSg==} + + swagger-ui-express@5.0.1: + resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} + engines: {node: '>= v0.10.32'} + peerDependencies: + express: '>=4.0.0 || >=5.0.0-beta' + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + terser-webpack-plugin@5.3.16: + resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tlds@1.261.0: + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} + hasBin: true + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-stream@1.0.0: + resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-jest@29.4.6: + resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + ts-loader@9.5.4: + resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typeorm@0.3.28: + resolution: {integrity: sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + upper-case@1.1.3: + resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + valid-data-url@3.0.1: + resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==} + engines: {node: '>=10'} + + validator@13.15.26: + resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + web-resource-inliner@6.0.1: + resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==} + engines: {node: '>=10.0.0'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + engines: {node: '>=10.13.0'} + + webpack@5.104.1: + resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + with@7.0.2: + resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} + engines: {node: '>= 10.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@3.2.0: + resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + +snapshots: + + '@angular-devkit/core@19.2.17(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/core@19.2.19(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics-cli@19.2.19(@types/node@22.19.11)(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) + '@inquirer/prompts': 7.3.2(@types/node@22.19.11) + ansi-colors: 4.1.3 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@19.2.17(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@19.2.19(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1019.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.25 + '@aws-sdk/credential-provider-node': 3.972.27 + '@aws-sdk/middleware-bucket-endpoint': 3.972.8 + '@aws-sdk/middleware-expect-continue': 3.972.8 + '@aws-sdk/middleware-flexible-checksums': 3.974.5 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-location-constraint': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.9 + '@aws-sdk/middleware-sdk-s3': 3.972.26 + '@aws-sdk/middleware-ssec': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/region-config-resolver': 3.972.10 + '@aws-sdk/signature-v4-multi-region': 3.996.14 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.12 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/eventstream-serde-browser': 4.2.12 + '@smithy/eventstream-serde-config-resolver': 4.3.12 + '@smithy/eventstream-serde-node': 4.2.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-blob-browser': 4.2.13 + '@smithy/hash-node': 4.2.12 + '@smithy/hash-stream-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/md5-js': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.13 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.25': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.16 + '@smithy/core': 3.23.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.5': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/credential-provider-env': 3.972.23 + '@aws-sdk/credential-provider-http': 3.972.25 + '@aws-sdk/credential-provider-login': 3.972.26 + '@aws-sdk/credential-provider-process': 3.972.23 + '@aws-sdk/credential-provider-sso': 3.972.26 + '@aws-sdk/credential-provider-web-identity': 3.972.26 + '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.27': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.23 + '@aws-sdk/credential-provider-http': 3.972.25 + '@aws-sdk/credential-provider-ini': 3.972.26 + '@aws-sdk/credential-provider-process': 3.972.23 + '@aws-sdk/credential-provider-sso': 3.972.26 + '@aws-sdk/credential-provider-web-identity': 3.972.26 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/token-providers': 3.1019.0 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.5': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.25 + '@aws-sdk/crc64-nvme': 3.972.5 + '@aws-sdk/types': 3.973.6 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.12 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.16': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.25 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/region-config-resolver': 3.972.10 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.12 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.14': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.26 + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1019.0': + dependencies: + '@aws-sdk/core': 3.973.25 + '@aws-sdk/nested-clients': 3.996.16 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.12': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.26 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.16': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': + optional: true + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@borewit/text-codec@0.2.1': {} + + '@cacheable/utils@2.3.4': + dependencies: + hashery: 1.5.0 + keyv: 5.6.0 + + '@colors/colors@1.5.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@css-inline/css-inline-android-arm-eabi@0.14.1': + optional: true + + '@css-inline/css-inline-android-arm64@0.14.1': + optional: true + + '@css-inline/css-inline-darwin-arm64@0.14.1': + optional: true + + '@css-inline/css-inline-darwin-x64@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm-gnueabihf@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm64-gnu@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm64-musl@0.14.1': + optional: true + + '@css-inline/css-inline-linux-x64-gnu@0.14.1': + optional: true + + '@css-inline/css-inline-linux-x64-musl@0.14.1': + optional: true + + '@css-inline/css-inline-win32-x64-msvc@0.14.1': + optional: true + + '@css-inline/css-inline@0.14.1': + optionalDependencies: + '@css-inline/css-inline-android-arm-eabi': 0.14.1 + '@css-inline/css-inline-android-arm64': 0.14.1 + '@css-inline/css-inline-darwin-arm64': 0.14.1 + '@css-inline/css-inline-darwin-x64': 0.14.1 + '@css-inline/css-inline-linux-arm-gnueabihf': 0.14.1 + '@css-inline/css-inline-linux-arm64-gnu': 0.14.1 + '@css-inline/css-inline-linux-arm64-musl': 0.14.1 + '@css-inline/css-inline-linux-x64-gnu': 0.14.1 + '@css-inline/css-inline-linux-x64-musl': 0.14.1 + '@css-inline/css-inline-win32-x64-msvc': 0.14.1 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@2.6.1))': + dependencies: + eslint: 9.39.3(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.3 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.4': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.3 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.3': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@fast-csv/format@5.0.5': + dependencies: + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.6': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.11) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/confirm@5.1.21(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/core@10.3.2(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.11) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/editor@4.2.23(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/expand@4.0.23(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/external-editor@1.0.3(@types/node@22.19.11)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/number@3.0.23(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/password@4.0.23(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/prompts@7.10.1(@types/node@22.19.11)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.19.11) + '@inquirer/confirm': 5.1.21(@types/node@22.19.11) + '@inquirer/editor': 4.2.23(@types/node@22.19.11) + '@inquirer/expand': 4.0.23(@types/node@22.19.11) + '@inquirer/input': 4.3.1(@types/node@22.19.11) + '@inquirer/number': 3.0.23(@types/node@22.19.11) + '@inquirer/password': 4.0.23(@types/node@22.19.11) + '@inquirer/rawlist': 4.1.11(@types/node@22.19.11) + '@inquirer/search': 3.2.2(@types/node@22.19.11) + '@inquirer/select': 4.4.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/prompts@7.3.2(@types/node@22.19.11)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.19.11) + '@inquirer/confirm': 5.1.21(@types/node@22.19.11) + '@inquirer/editor': 4.2.23(@types/node@22.19.11) + '@inquirer/expand': 4.0.23(@types/node@22.19.11) + '@inquirer/input': 4.3.1(@types/node@22.19.11) + '@inquirer/number': 3.0.23(@types/node@22.19.11) + '@inquirer/password': 4.0.23(@types/node@22.19.11) + '@inquirer/rawlist': 4.1.11(@types/node@22.19.11) + '@inquirer/search': 3.2.2(@types/node@22.19.11) + '@inquirer/select': 4.4.2(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/rawlist@4.1.11(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/search@3.2.2(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.11) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/select@4.4.2(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.11) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/type@3.0.10(@types/node@22.19.11)': + optionalDependencies: + '@types/node': 22.19.11 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.19.11 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 22.19.11 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.19.11 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/redis@5.1.6(keyv@5.6.0)': + dependencies: + '@redis/client': 5.11.0 + cluster-key-slot: 1.1.2 + hookified: 1.15.1 + keyv: 5.6.0 + transitivePeerDependencies: + - '@node-rs/xxhash' + + '@keyv/serialize@1.1.1': {} + + '@lukeed/csprng@1.1.0': {} + + '@microsoft/tsdoc@0.16.0': {} + + '@napi-rs/nice-android-arm-eabi@1.1.1': + optional: true + + '@napi-rs/nice-android-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.1.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.1.1': + optional: true + + '@napi-rs/nice-openharmony-arm64@1.1.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + optional: true + + '@napi-rs/nice@1.1.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 + optional: true + + '@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(nodemailer@8.0.1)': + dependencies: + '@css-inline/css-inline': 0.14.1 + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + glob: 10.3.12 + nodemailer: 8.0.1 + optionalDependencies: + '@types/ejs': 3.1.5 + '@types/mjml': 4.7.4 + '@types/pug': 2.0.10 + ejs: 3.1.10 + handlebars: 4.7.8 + liquidjs: 10.24.0 + mjml: 4.18.0 + preview-email: 3.1.1 + pug: 3.0.3 + transitivePeerDependencies: + - encoding + + '@nestjs/axios@4.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.5)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + axios: 1.13.5 + rxjs: 7.8.2 + + '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cache-manager: 7.2.8 + keyv: 5.6.0 + rxjs: 7.8.2 + + '@nestjs/cli@11.0.16(@swc/cli@0.6.0(@swc/core@1.15.13)(chokidar@4.0.3))(@swc/core@1.15.13)(@types/node@22.19.11)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics-cli': 19.2.19(@types/node@22.19.11)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@22.19.11) + '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.9.3) + ansis: 4.2.0 + chokidar: 4.0.3 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.13)) + glob: 13.0.0 + node-emoji: 1.11.0 + ora: 5.4.1 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.9.3 + webpack: 5.104.1(@swc/core@1.15.13) + webpack-node-externals: 3.0.0 + optionalDependencies: + '@swc/cli': 0.6.0(@swc/core@1.15.13)(chokidar@4.0.3) + '@swc/core': 1.15.13 + transitivePeerDependencies: + - '@types/node' + - esbuild + - uglify-js + - webpack-cli + + '@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + file-type: 21.3.0 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.3 + transitivePeerDependencies: + - supports-color + + '@nestjs/config@4.0.3(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 17.2.3 + dotenv-expand: 12.0.3 + lodash: 4.17.23 + rxjs: 7.8.2 + + '@nestjs/core@11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nuxt/opencollective': 0.4.1 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14) + + '@nestjs/event-emitter@3.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + eventemitter2: 6.4.9 + + '@nestjs/jwt@11.0.2(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@types/jsonwebtoken': 9.0.10 + jsonwebtoken: 9.0.3 + + '@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.3 + + '@nestjs/passport@11.0.5(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + passport: 0.7.0 + + '@nestjs/platform-express@11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cors: 2.8.6 + express: 5.2.1 + multer: 2.0.2 + path-to-regexp: 8.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/schedule@6.1.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: 4.4.0 + + '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': + dependencies: + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.17(chokidar@4.0.3) + comment-json: 4.4.1 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar + + '@nestjs/swagger@11.2.6(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.1.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2) + js-yaml: 4.1.1 + lodash: 4.17.23 + path-to-regexp: 8.3.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.31.0 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.3 + + '@nestjs/terminus@11.1.1(@nestjs/axios@4.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.5)(rxjs@7.8.2))(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@nestjs/axios': 4.0.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.5)(rxjs@7.8.2) + '@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3))) + typeorm: 0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + + '@nestjs/testing@11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(@nestjs/platform-express@11.1.14)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + optionalDependencies: + '@nestjs/platform-express': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14) + + '@nestjs/throttler@6.5.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + + '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.14(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.14)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + typeorm: 0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nuxt/opencollective@0.4.1': + dependencies: + consola: 3.4.2 + + '@one-ini/wasm@0.1.1': + optional: true + + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@pinojs/redact@0.4.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + optional: true + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + optional: true + + '@redis/client@5.11.0': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + optional: true + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + optional: true + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + optional: true + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + optional: true + + '@scarf/scarf@1.4.0': {} + + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + optional: true + + '@sinclair/typebox@0.27.10': {} + + '@sindresorhus/is@5.6.0': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@smithy/abort-controller@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/core@3.23.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.12': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.12': + dependencies: + '@smithy/eventstream-codec': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.13': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.27': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-serde': 4.2.15 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.44': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.15': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.5.0': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/smithy-client@4.12.7': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.43': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.47': + dependencies: + '@smithy/config-resolver': 4.4.13 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.12': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.20': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.13': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + + '@sqltools/formatter@1.2.5': {} + + '@standard-schema/spec@1.1.0': {} + + '@stellar/js-xdr@3.1.2': {} + + '@stellar/stellar-base@14.0.4': + dependencies: + '@noble/curves': 1.9.7 + '@stellar/js-xdr': 3.1.2 + base32.js: 0.1.0 + bignumber.js: 9.3.1 + buffer: 6.0.3 + sha.js: 2.4.12 + + '@stellar/stellar-sdk@14.5.0': + dependencies: + '@stellar/stellar-base': 14.0.4 + axios: 1.13.5 + bignumber.js: 9.3.1 + commander: 14.0.3 + eventsource: 2.0.2 + feaxios: 0.0.23 + randombytes: 2.1.0 + toml: 3.0.0 + urijs: 1.19.11 + transitivePeerDependencies: + - debug + + '@swc/cli@0.6.0(@swc/core@1.15.13)(chokidar@4.0.3)': + dependencies: + '@swc/core': 1.15.13 + '@swc/counter': 0.1.3 + '@xhmikosr/bin-wrapper': 13.2.0 + commander: 8.3.0 + fast-glob: 3.3.3 + minimatch: 9.0.6 + piscina: 4.9.2 + semver: 7.7.4 + slash: 3.0.0 + source-map: 0.7.6 + optionalDependencies: + chokidar: 4.0.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@swc/core-darwin-arm64@1.15.13': + optional: true + + '@swc/core-darwin-x64@1.15.13': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.13': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.13': + optional: true + + '@swc/core-linux-arm64-musl@1.15.13': + optional: true + + '@swc/core-linux-x64-gnu@1.15.13': + optional: true + + '@swc/core-linux-x64-musl@1.15.13': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.13': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.13': + optional: true + + '@swc/core-win32-x64-msvc@1.15.13': + optional: true + + '@swc/core@1.15.13': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.13 + '@swc/core-darwin-x64': 1.15.13 + '@swc/core-linux-arm-gnueabihf': 1.15.13 + '@swc/core-linux-arm64-gnu': 1.15.13 + '@swc/core-linux-arm64-musl': 1.15.13 + '@swc/core-linux-x64-gnu': 1.15.13 + '@swc/core-linux-x64-musl': 1.15.13 + '@swc/core-win32-arm64-msvc': 1.15.13 + '@swc/core-win32-ia32-msvc': 1.15.13 + '@swc/core-win32-x64-msvc': 1.15.13 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@szmarczak/http-timer@5.0.1': + dependencies: + defer-to-connect: 2.0.1 + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3 + fflate: 0.8.2 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/bcrypt@6.0.0': + dependencies: + '@types/node': 22.19.11 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 22.19.11 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.19.11 + + '@types/cookiejar@2.1.5': {} + + '@types/ejs@3.1.5': + optional: true + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 22.19.11 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.19.11 + + '@types/http-cache-semantics@4.2.0': {} + + '@types/http-errors@2.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.19.11 + + '@types/luxon@3.7.1': {} + + '@types/methods@1.1.4': {} + + '@types/mjml-core@4.15.2': + optional: true + + '@types/mjml@4.7.4': + dependencies: + '@types/mjml-core': 4.15.2 + optional: true + + '@types/ms@2.1.0': {} + + '@types/multer@2.0.0': + dependencies: + '@types/express': 5.0.6 + + '@types/node@22.19.11': + dependencies: + undici-types: 6.21.0 + + '@types/nodemailer@7.0.11': + dependencies: + '@types/node': 22.19.11 + + '@types/passport-jwt@4.0.1': + dependencies: + '@types/jsonwebtoken': 9.0.10 + '@types/passport-strategy': 0.2.38 + + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 5.0.6 + '@types/passport': 1.0.17 + + '@types/passport@1.0.17': + dependencies: + '@types/express': 5.0.6 + + '@types/pug@2.0.10': + optional: true + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 22.19.11 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.19.11 + + '@types/stack-utils@2.0.3': {} + + '@types/superagent@8.1.9': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 22.19.11 + form-data: 4.0.5 + + '@types/supertest@6.0.3': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 + + '@types/uuid@11.0.0': + dependencies: + uuid: 13.0.0 + + '@types/validator@13.15.10': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 9.39.3(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 9.39.3(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.3(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.2 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 9.39.3(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xhmikosr/archive-type@7.1.0': + dependencies: + file-type: 20.5.0 + transitivePeerDependencies: + - supports-color + + '@xhmikosr/bin-check@7.1.0': + dependencies: + execa: 5.1.1 + isexe: 2.0.0 + + '@xhmikosr/bin-wrapper@13.2.0': + dependencies: + '@xhmikosr/bin-check': 7.1.0 + '@xhmikosr/downloader': 15.2.0 + '@xhmikosr/os-filter-obj': 3.0.0 + bin-version-check: 5.1.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-tar@8.1.0': + dependencies: + file-type: 20.5.0 + is-stream: 2.0.1 + tar-stream: 3.1.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-tarbz2@8.1.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 + is-stream: 2.0.1 + seek-bzip: 2.0.0 + unbzip2-stream: 1.4.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-targz@8.1.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 + is-stream: 2.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-unzip@7.1.0': + dependencies: + file-type: 20.5.0 + get-stream: 6.0.1 + yauzl: 3.2.0 + transitivePeerDependencies: + - supports-color + + '@xhmikosr/decompress@10.2.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + '@xhmikosr/decompress-tarbz2': 8.1.0 + '@xhmikosr/decompress-targz': 8.1.0 + '@xhmikosr/decompress-unzip': 7.1.0 + graceful-fs: 4.2.11 + strip-dirs: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/downloader@15.2.0': + dependencies: + '@xhmikosr/archive-type': 7.1.0 + '@xhmikosr/decompress': 10.2.0 + content-disposition: 0.5.4 + defaults: 2.0.2 + ext-name: 5.0.0 + file-type: 20.5.0 + filenamify: 6.0.0 + get-stream: 6.0.1 + got: 13.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/os-filter-obj@3.0.0': + dependencies: + arch: 3.0.0 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + '@zone-eu/mailsplit@5.4.8': + dependencies: + libbase64: 1.3.0 + libmime: 5.3.7 + libqp: 2.1.1 + optional: true + + abbrev@2.0.0: + optional: true + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-phases@1.0.4(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@7.4.1: + optional: true + + acorn@8.16.0: {} + + ajv-formats@2.1.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.14.0): + dependencies: + ajv: 6.14.0 + + ajv-keywords@5.1.0(ajv@8.18.0): + dependencies: + ajv: 8.18.0 + fast-deep-equal: 3.1.3 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + alce@1.2.0: + dependencies: + esprima: 1.2.5 + estraverse: 1.9.3 + optional: true + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + app-root-path@3.1.0: {} + + append-field@1.0.0: {} + + arch@3.0.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-timsort@1.0.3: {} + + asap@2.0.6: {} + + assert-never@1.4.0: + optional: true + + async@3.2.6: + optional: true + + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.13.5: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + b4a@1.8.0: {} + + babel-jest@29.7.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + + babel-walk@3.0.0-canary-5: + dependencies: + '@babel/types': 7.29.0 + optional: true + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + bare-events@2.8.2: {} + + base32.js@0.1.0: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.0: {} + + bcrypt@6.0.0: + dependencies: + node-addon-api: 8.5.0 + node-gyp-build: 4.8.4 + + bignumber.js@9.3.1: {} + + bin-version-check@5.1.0: + dependencies: + bin-version: 6.0.0 + semver: 7.7.4 + semver-truncate: 3.0.0 + + bin-version@6.0.0: + dependencies: + execa: 5.1.1 + find-versions: 5.1.0 + + binary-extensions@2.3.0: + optional: true + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + boolbase@1.0.0: + optional: true + + bowser@2.14.1: {} + + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + optional: true + + brace-expansion@5.0.3: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001774 + electron-to-chromium: 1.5.302 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-crc32@0.2.13: {} + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + cache-manager@7.2.8: + dependencies: + '@cacheable/utils': 2.3.4 + keyv: 5.6.0 + + cacheable-lookup@7.0.0: {} + + cacheable-request@10.2.14: + dependencies: + '@types/http-cache-semantics': 4.2.0 + get-stream: 6.0.1 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.1.1 + responselike: 3.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camel-case@3.0.0: + dependencies: + no-case: 2.3.2 + upper-case: 1.1.3 + optional: true + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001774: {} + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + optional: true + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + character-parser@2.2.0: + dependencies: + is-regex: 1.2.1 + optional: true + + chardet@2.1.1: {} + + check-disk-space@3.4.0: {} + + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.2.2 + css-what: 6.2.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + optional: true + + cheerio@1.0.0-rc.12: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + htmlparser2: 8.0.2 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + optional: true + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + optional: true + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chrome-trace-event@1.0.4: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.3: + dependencies: + '@types/validator': 13.15.10 + libphonenumber-js: 1.12.37 + validator: 13.15.26 + + clean-css@4.2.4: + dependencies: + source-map: 0.6.1 + optional: true + + cli-boxes@2.2.1: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + cluster-key-slot@1.1.2: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: + optional: true + + commander@14.0.3: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@6.2.1: {} + + commander@8.3.0: {} + + comment-json@4.4.1: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + optional: true + + consola@3.4.2: {} + + constantinople@4.0.1: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + optional: true + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookiejar@2.1.4: {} + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + create-jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + cron@4.4.0: + dependencies: + '@types/luxon': 3.7.1 + luxon: 3.7.2 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + optional: true + + css-what@6.2.2: + optional: true + + dateformat@4.6.3: {} + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + dedent@1.7.1: {} + + deep-extend@0.6.0: + optional: true + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + defaults@2.0.2: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + detect-indent@6.1.0: + optional: true + + detect-newline@3.1.0: {} + + detect-node@2.1.0: + optional: true + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff-sequences@29.6.3: {} + + diff@4.0.4: {} + + display-notification@2.0.0: + dependencies: + escape-string-applescript: 1.0.0 + run-applescript: 3.2.0 + optional: true + + doctypes@1.1.0: + optional: true + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + optional: true + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + optional: true + + domelementtype@2.3.0: + optional: true + + domhandler@3.3.0: + dependencies: + domelementtype: 2.3.0 + optional: true + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + optional: true + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + optional: true + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + optional: true + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + optional: true + + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.6.1 + + dotenv@16.6.1: {} + + dotenv@17.2.3: {} + + dotenv@17.3.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.4 + optional: true + + ee-first@1.1.1: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + optional: true + + electron-to-chromium@1.5.302: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + encoding-japanese@2.2.0: + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.19.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@2.2.0: + optional: true + + entities@4.5.0: + optional: true + + entities@6.0.1: + optional: true + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.0.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + + escape-goat@3.0.0: + optional: true + + escape-html@1.0.3: {} + + escape-string-applescript@1.0.0: + optional: true + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)): + dependencies: + eslint: 9.39.3(jiti@2.6.1) + + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1))(prettier@3.8.1): + dependencies: + eslint: 9.39.3(jiti@2.6.1) + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 10.1.8(eslint@9.39.3(jiti@2.6.1)) + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.3(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.4 + '@eslint/js': 9.39.3 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.3 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esprima@1.2.5: + optional: true + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@1.9.3: + optional: true + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter2@6.4.9: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + eventsource@2.0.2: {} + + execa@0.10.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + optional: true + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + ext-list@2.2.2: + dependencies: + mime-db: 1.54.0 + + ext-name@5.0.0: + dependencies: + ext-list: 2.2.2 + sort-keys-length: 1.0.1 + + extend-object@1.0.0: + optional: true + + fast-check@4.6.0: + dependencies: + pure-rand: 8.4.0 + + fast-copy@4.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.0 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.0 + strnum: 2.2.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + feaxios@0.0.23: + dependencies: + is-retry-allowed: 3.0.0 + + fflate@0.8.2: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-type@20.5.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + file-type@21.3.0: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.4 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + filelist@1.0.5: + dependencies: + minimatch: 10.2.2 + optional: true + + filename-reserved-regex@3.0.0: {} + + filenamify@6.0.0: + dependencies: + filename-reserved-regex: 3.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-versions@5.1.0: + dependencies: + semver-regex: 4.0.5 + + fixpack@4.0.0: + dependencies: + alce: 1.2.0 + chalk: 3.0.0 + detect-indent: 6.1.0 + detect-newline: 3.1.0 + extend-object: 1.0.0 + rc: 1.2.8 + optional: true + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.13)): + dependencies: + '@babel/code-frame': 7.29.0 + chalk: 4.1.2 + chokidar: 4.0.3 + cosmiconfig: 8.3.6(typescript@5.9.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.3 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.4 + tapable: 2.3.0 + typescript: 5.9.3 + webpack: 5.104.1(@swc/core@1.15.13) + + form-data-encoder@2.1.4: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-monkey@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + generic-pool@3.9.0: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-port@5.1.1: + optional: true + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@3.0.0: + optional: true + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.3.12: + dependencies: + foreground-child: 3.3.1 + jackspeak: 2.3.6 + minimatch: 9.0.6 + minipass: 7.1.3 + path-scurry: 1.11.1 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.6 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@13.0.0: + dependencies: + minimatch: 10.2.2 + minipass: 7.1.3 + path-scurry: 2.0.2 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.3 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@16.5.0: {} + + gopd@1.2.0: {} + + got@13.0.0: + dependencies: + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + + graceful-fs@4.2.11: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashery@1.5.0: + dependencies: + hookified: 1.15.1 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: + optional: true + + helmet@8.1.0: {} + + help-me@5.0.0: {} + + hookified@1.15.1: {} + + html-escaper@2.0.2: {} + + html-minifier@4.0.0: + dependencies: + camel-case: 3.0.0 + clean-css: 4.2.4 + commander: 2.20.3 + he: 1.2.0 + param-case: 2.1.1 + relateurl: 0.2.7 + uglify-js: 3.19.3 + optional: true + + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + optional: true + + htmlparser2@5.0.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 + optional: true + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + optional: true + + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + optional: true + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: + optional: true + + inspect-with-kind@1.0.5: + dependencies: + kind-of: 6.0.3 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + optional: true + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: + optional: true + + is-expression@4.0.0: + dependencies: + acorn: 7.4.1 + object-assign: 4.1.1 + optional: true + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-plain-obj@1.1.0: {} + + is-promise@2.2.2: + optional: true + + is-promise@4.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + optional: true + + is-retry-allowed@3.0.0: {} + + is-stream@1.1.0: + optional: true + + is-stream@2.0.1: {} + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unicode-supported@0.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + optional: true + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterare@1.2.1: {} + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.5 + picocolors: 1.1.1 + optional: true + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.19.11 + ts-node: 10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.19.11 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.11 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.11 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 22.19.11 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.19.11 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@2.6.1: + optional: true + + joi@18.0.2: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.6 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.1.0 + + joycon@3.1.1: {} + + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 + optional: true + + js-cookie@3.0.5: + optional: true + + js-stringify@1.0.2: + optional: true + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jstransformer@1.0.0: + dependencies: + is-promise: 2.2.2 + promise: 7.3.1 + optional: true + + juice@10.0.1: + dependencies: + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 6.0.1 + transitivePeerDependencies: + - encoding + optional: true + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + leac@0.6.0: + optional: true + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libbase64@1.3.0: + optional: true + + libmime@5.3.7: + dependencies: + encoding-japanese: 2.2.0 + iconv-lite: 0.6.3 + libbase64: 1.3.0 + libqp: 2.1.1 + optional: true + + libphonenumber-js@1.12.37: {} + + libqp@2.1.1: + optional: true + + lines-and-columns@1.2.4: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + optional: true + + liquidjs@10.24.0: + dependencies: + commander: 10.0.1 + optional: true + + load-esm@1.0.3: {} + + loader-runner@4.3.1: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.escaperegexp@4.1.2: {} + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isfunction@3.0.9: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnil@4.0.0: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lower-case@1.1.4: + optional: true + + lowercase-keys@3.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.6: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + luxon@3.7.2: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mailparser@3.9.3: + dependencies: + '@zone-eu/mailsplit': 5.4.8 + encoding-japanese: 2.2.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.7.2 + libmime: 5.3.7 + linkify-it: 5.0.0 + nodemailer: 7.0.13 + punycode.js: 2.3.1 + tlds: 1.261.0 + optional: true + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + mensch@0.3.4: + optional: true + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-response@3.1.0: {} + + mimic-response@4.0.0: {} + + minimatch@10.2.2: + dependencies: + brace-expansion: 5.0.3 + + minimatch@3.1.3: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + optional: true + + minimatch@9.0.6: + dependencies: + brace-expansion: 5.0.3 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mjml-accordion@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-body@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-button@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-carousel@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-cli@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + chokidar: 3.6.0 + glob: 10.3.12 + html-minifier: 4.0.0 + js-beautify: 1.15.4 + lodash: 4.17.23 + minimatch: 9.0.6 + mjml-core: 4.18.0 + mjml-migrate: 4.18.0 + mjml-parser-xml: 4.18.0 + mjml-validator: 4.18.0 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + optional: true + + mjml-column@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-core@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + cheerio: 1.0.0-rc.12 + detect-node: 2.1.0 + html-minifier: 4.0.0 + js-beautify: 1.15.4 + juice: 10.0.1 + lodash: 4.17.23 + mjml-migrate: 4.18.0 + mjml-parser-xml: 4.18.0 + mjml-validator: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-divider@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-group@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-attributes@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-breakpoint@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-font@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-html-attributes@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-preview@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-style@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-title@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-hero@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-image@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-migrate@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + js-beautify: 1.15.4 + lodash: 4.17.23 + mjml-core: 4.18.0 + mjml-parser-xml: 4.18.0 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + optional: true + + mjml-navbar@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-parser-xml@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + detect-node: 2.1.0 + htmlparser2: 9.1.0 + lodash: 4.17.23 + optional: true + + mjml-preset-core@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + mjml-accordion: 4.18.0 + mjml-body: 4.18.0 + mjml-button: 4.18.0 + mjml-carousel: 4.18.0 + mjml-column: 4.18.0 + mjml-divider: 4.18.0 + mjml-group: 4.18.0 + mjml-head: 4.18.0 + mjml-head-attributes: 4.18.0 + mjml-head-breakpoint: 4.18.0 + mjml-head-font: 4.18.0 + mjml-head-html-attributes: 4.18.0 + mjml-head-preview: 4.18.0 + mjml-head-style: 4.18.0 + mjml-head-title: 4.18.0 + mjml-hero: 4.18.0 + mjml-image: 4.18.0 + mjml-navbar: 4.18.0 + mjml-raw: 4.18.0 + mjml-section: 4.18.0 + mjml-social: 4.18.0 + mjml-spacer: 4.18.0 + mjml-table: 4.18.0 + mjml-text: 4.18.0 + mjml-wrapper: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-raw@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-section@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-social@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-spacer@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-table@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-text@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml-validator@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + optional: true + + mjml-wrapper@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + lodash: 4.17.23 + mjml-core: 4.18.0 + mjml-section: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mjml@4.18.0: + dependencies: + '@babel/runtime': 7.28.6 + mjml-cli: 4.18.0 + mjml-core: 4.18.0 + mjml-migrate: 4.18.0 + mjml-preset-core: 4.18.0 + mjml-validator: 4.18.0 + transitivePeerDependencies: + - encoding + optional: true + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + ms@2.1.3: {} + + multer@2.0.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@2.0.0: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + neo-async@2.6.2: {} + + nestjs-pino@4.6.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2): + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + pino: 10.3.1 + pino-http: 11.0.0 + rxjs: 7.8.2 + + nice-try@1.0.5: + optional: true + + no-case@2.3.2: + dependencies: + lower-case: 1.1.4 + optional: true + + node-abort-controller@3.1.1: {} + + node-addon-api@8.5.0: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.17.23 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + optional: true + + node-gyp-build@4.8.4: {} + + node-int64@0.4.0: {} + + node-releases@2.0.27: {} + + nodemailer@7.0.13: + optional: true + + nodemailer@8.0.1: {} + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + optional: true + + normalize-path@3.0.0: {} + + normalize-url@8.1.1: {} + + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + optional: true + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + optional: true + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + optional: true + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + p-cancelable@3.0.0: {} + + p-event@4.2.0: + dependencies: + p-timeout: 3.2.0 + optional: true + + p-finally@1.0.0: + optional: true + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + optional: true + + p-try@2.2.0: {} + + p-wait-for@3.2.0: + dependencies: + p-timeout: 3.2.0 + optional: true + + package-json-from-dist@1.0.1: {} + + param-case@2.1.1: + dependencies: + no-case: 2.3.2 + optional: true + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.3.0 + optional: true + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + optional: true + + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + optional: true + + parseurl@1.3.3: {} + + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.3 + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + + path-exists@4.0.0: {} + + path-expression-matcher@1.2.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: + optional: true + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.6 + minipass: 7.1.3 + + path-to-regexp@8.3.0: {} + + path-type@4.0.0: {} + + pause@0.0.1: {} + + peberminta@0.9.0: + optional: true + + pend@1.2.0: {} + + pg-cloudflare@1.3.0: + optional: true + + pg-connection-string@2.11.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.11.0(pg@8.18.0): + dependencies: + pg: 8.18.0 + + pg-protocol@1.11.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.18.0: + dependencies: + pg-connection-string: 2.11.0 + pg-pool: 3.11.0(pg@8.18.0) + pg-protocol: 1.11.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.3.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + picomatch@4.0.3: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-http@11.0.0: + dependencies: + get-caller-file: 2.0.5 + pino: 10.3.1 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + + pirates@4.0.7: {} + + piscina@4.9.2: + optionalDependencies: + '@napi-rs/nice': 1.1.1 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pluralize@8.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + preview-email@3.1.1: + dependencies: + ci-info: 3.9.0 + display-notification: 2.0.0 + fixpack: 4.0.0 + get-port: 5.1.1 + mailparser: 3.9.3 + nodemailer: 7.0.13 + open: 7.4.2 + p-event: 4.2.0 + p-wait-for: 3.2.0 + pug: 3.0.3 + uuid: 9.0.1 + optional: true + + process-warning@5.0.0: {} + + promise@7.3.1: + dependencies: + asap: 2.0.6 + optional: true + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proto-list@1.2.4: + optional: true + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + pug-attrs@3.0.0: + dependencies: + constantinople: 4.0.1 + js-stringify: 1.0.2 + pug-runtime: 3.0.1 + optional: true + + pug-code-gen@3.0.3: + dependencies: + constantinople: 4.0.1 + doctypes: 1.1.0 + js-stringify: 1.0.2 + pug-attrs: 3.0.0 + pug-error: 2.1.0 + pug-runtime: 3.0.1 + void-elements: 3.1.0 + with: 7.0.2 + optional: true + + pug-error@2.1.0: + optional: true + + pug-filters@4.0.0: + dependencies: + constantinople: 4.0.1 + jstransformer: 1.0.0 + pug-error: 2.1.0 + pug-walk: 2.0.0 + resolve: 1.22.11 + optional: true + + pug-lexer@5.0.1: + dependencies: + character-parser: 2.2.0 + is-expression: 4.0.0 + pug-error: 2.1.0 + optional: true + + pug-linker@4.0.0: + dependencies: + pug-error: 2.1.0 + pug-walk: 2.0.0 + optional: true + + pug-load@3.0.0: + dependencies: + object-assign: 4.1.1 + pug-walk: 2.0.0 + optional: true + + pug-parser@6.0.0: + dependencies: + pug-error: 2.1.0 + token-stream: 1.0.0 + optional: true + + pug-runtime@3.0.1: + optional: true + + pug-strip-comments@2.0.0: + dependencies: + pug-error: 2.1.0 + optional: true + + pug-walk@2.0.0: + optional: true + + pug@3.0.3: + dependencies: + pug-code-gen: 3.0.3 + pug-filters: 4.0.0 + pug-lexer: 5.0.1 + pug-linker: 4.0.0 + pug-load: 3.0.0 + pug-parser: 6.0.0 + pug-runtime: 3.0.1 + pug-strip-comments: 2.0.0 + optional: true + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode.js@2.3.1: + optional: true + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + pure-rand@8.4.0: {} + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + quick-format-unescaped@4.0.4: {} + + quick-lru@5.1.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + optional: true + + react-is@18.3.1: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + optional: true + + readdirp@4.1.2: {} + + real-require@0.2.0: {} + + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + optional: true + + reflect-metadata@0.2.2: {} + + relateurl@0.2.7: + optional: true + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-alpn@1.2.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@3.0.0: + dependencies: + lowercase-keys: 3.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + run-applescript@3.2.0: + dependencies: + execa: 0.10.0 + optional: true + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.14.0 + ajv-keywords: 3.5.2(ajv@6.14.0) + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) + + secure-json-parse@4.1.0: {} + + seek-bzip@2.0.0: + dependencies: + commander: 6.2.1 + + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + optional: true + + semver-regex@4.0.5: {} + + semver-truncate@3.0.0: + dependencies: + semver: 7.7.4 + + semver@5.7.2: + optional: true + + semver@6.3.1: {} + + semver@7.7.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: + optional: true + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slick@1.12.2: + optional: true + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + sort-keys-length@1.0.1: + dependencies: + sort-keys: 1.1.2 + + sort-keys@1.1.2: + dependencies: + is-plain-obj: 1.1.0 + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + source-map@0.7.6: {} + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + sql-highlight@6.1.0: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + statuses@2.0.2: {} + + streamsearch@1.1.0: {} + + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-dirs@3.0.0: + dependencies: + inspect-with-kind: 1.0.5 + is-plain-obj: 1.1.0 + + strip-eof@1.0.0: + optional: true + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: + optional: true + + strip-json-comments@3.1.1: {} + + strip-json-comments@5.0.3: {} + + strnum@2.2.2: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3 + fast-safe-stringify: 2.1.1 + form-data: 4.0.5 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.15.0 + transitivePeerDependencies: + - supports-color + + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swagger-ui-dist@5.31.0: + dependencies: + '@scarf/scarf': 1.4.0 + + swagger-ui-dist@5.31.2: + dependencies: + '@scarf/scarf': 1.4.0 + + swagger-ui-express@5.0.1(express@5.2.1): + dependencies: + express: 5.2.1 + swagger-ui-dist: 5.31.2 + + symbol-observable@4.0.0: {} + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + tapable@2.3.0: {} + + tar-stream@3.1.7: + dependencies: + b4a: 1.8.0 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + terser-webpack-plugin@5.3.16(@swc/core@1.15.13)(webpack@5.104.1(@swc/core@1.15.13)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.46.0 + webpack: 5.104.1(@swc/core@1.15.13) + optionalDependencies: + '@swc/core': 1.15.13 + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.3 + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.0 + transitivePeerDependencies: + - react-native-b4a + + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + + through@2.3.8: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tlds@1.261.0: + optional: true + + tmpl@1.0.5: {} + + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-stream@1.0.0: + optional: true + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + toml@3.0.0: {} + + tr46@0.0.3: + optional: true + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.4 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + jest-util: 29.7.0 + + ts-loader@9.5.4(typescript@5.9.3)(webpack@5.104.1(@swc/core@1.15.13)): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.19.0 + micromatch: 4.0.8 + semver: 7.7.4 + source-map: 0.7.6 + typescript: 5.9.3 + webpack: 5.104.1(@swc/core@1.15.13) + + ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.19.11 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.15.13 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.19.0 + tapable: 2.3.0 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typedarray@0.0.6: {} + + typeorm@0.3.28(pg@8.18.0)(redis@4.7.1)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)): + dependencies: + '@sqltools/formatter': 1.2.5 + ansis: 4.2.0 + app-root-path: 3.1.0 + buffer: 6.0.3 + dayjs: 1.11.19 + debug: 4.4.3 + dedent: 1.7.1 + dotenv: 16.6.1 + glob: 10.5.0 + reflect-metadata: 0.2.2 + sha.js: 2.4.12 + sql-highlight: 6.1.0 + tslib: 2.8.1 + uuid: 11.1.0 + yargs: 17.7.2 + optionalDependencies: + pg: 8.18.0 + redis: 4.7.1 + ts-node: 10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + typescript-eslint@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.3(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + uc.micro@2.1.0: + optional: true + + uglify-js@3.19.3: + optional: true + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.5.0: {} + + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + upper-case@1.1.3: + optional: true + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urijs@1.19.11: {} + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@11.1.0: {} + + uuid@13.0.0: {} + + uuid@9.0.1: + optional: true + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + valid-data-url@3.0.1: + optional: true + + validator@13.15.26: {} + + vary@1.1.2: {} + + void-elements@3.1.0: + optional: true + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-resource-inliner@6.0.1: + dependencies: + ansi-colors: 4.1.3 + escape-goat: 3.0.0 + htmlparser2: 5.0.1 + mime: 2.6.0 + node-fetch: 2.7.0 + valid-data-url: 3.0.1 + transitivePeerDependencies: + - encoding + optional: true + + webidl-conversions@3.0.1: + optional: true + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.3.4: {} + + webpack@5.104.1(@swc/core@1.15.13): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.19.0 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(@swc/core@1.15.13)(webpack@5.104.1(@swc/core@1.15.13)) + watchpack: 2.5.1 + webpack-sources: 3.3.4 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + optional: true + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + optional: true + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + with@7.0.2: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + assert-never: 1.4.0 + babel-walk: 3.0.0-canary-5 + optional: true + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: + optional: true + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.3: {} diff --git a/backend/src/Implement Role-Based Access Control (RBAC)/roles.guard.spec.ts b/backend/src/Implement Role-Based Access Control (RBAC)/roles.guard.spec.ts index 68540984c..e871c59c5 100644 --- a/backend/src/Implement Role-Based Access Control (RBAC)/roles.guard.spec.ts +++ b/backend/src/Implement Role-Based Access Control (RBAC)/roles.guard.spec.ts @@ -28,7 +28,7 @@ describe('RolesGuard', () => { }); const mockReflector = (roles: Role[] | undefined) => { - jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(roles); + jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(roles as any); }; it('allows access when no @Roles decorator is present', () => { diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 631122b28..af65d1de5 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -8,8 +8,6 @@ import { AuditLogInterceptor } from './common/interceptors/audit-log.interceptor import { RequestLoggingInterceptor } from './common/interceptors/request-logging.interceptor'; import { GracefulShutdownInterceptor } from './common/interceptors/graceful-shutdown.interceptor'; import { TieredThrottlerGuard } from './common/guards/tiered-throttler.guard'; -import { ApmModule } from './modules/apm/apm.module'; -import { ApmInterceptor } from './modules/apm/apm.interceptor'; import { CommonModule } from './common/common.module'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { LoggerModule } from 'nestjs-pino'; @@ -39,7 +37,6 @@ import { NotificationsModule } from './modules/notifications/notifications.modul import { TransactionsModule } from './modules/transactions/transactions.module'; import { ReportsModule } from './modules/reports/reports.module'; import { ReferralsModule } from './modules/referrals/referrals.module'; -import { RewardsModule } from './modules/rewards/rewards.module'; import { TestRbacModule } from './test-rbac/test-rbac.module'; import { TestThrottlingModule } from './test-throttling/test-throttling.module'; import { ApiVersioningModule } from './common/versioning/api-versioning.module'; @@ -49,7 +46,6 @@ import { ConnectionPoolModule } from './common/database/connection-pool.module'; import { CircuitBreakerModule } from './common/circuit-breaker/circuit-breaker.module'; import { PostmanModule } from './common/postman/postman.module'; import { CorrelationIdMiddleware } from './common/middleware/correlation-id.middleware'; -import { RequestValidationMiddleware } from './common/middleware/validation.middleware'; import { PerformanceModule } from './modules/performance/performance.module'; import { GracefulShutdownService } from './common/services/graceful-shutdown.service'; @@ -98,10 +94,6 @@ const envValidationSchema = Joi.object({ BACKUP_ENCRYPTION_KEY: Joi.string().length(64).optional(), // 32-byte key as hex BACKUP_RETENTION_DAYS: Joi.number().integer().min(1).default(30).optional(), BACKUP_TMP_DIR: Joi.string().optional(), - - APM_SAMPLING_RATE: Joi.number().min(0).max(1).default(1.0).optional(), - APM_ENABLED: Joi.boolean().default(true).optional(), - ALLOWED_ORIGINS: Joi.string().optional(), }); @Module({ @@ -156,30 +148,6 @@ const envValidationSchema = Joi.object({ useFactory: (configService: ConfigService) => { const dbUrl = configService.get('database.url'); const dbHost = configService.get('database.host'); - const isProduction = configService.get('NODE_ENV') === 'production'; - const redisUrl = configService.get('REDIS_URL'); - - const poolConfig = { - max: configService.get('DATABASE_POOL_MAX', isProduction ? 30 : 10), - min: configService.get('DATABASE_POOL_MIN', isProduction ? 5 : 2), - idleTimeoutMillis: configService.get('DATABASE_IDLE_TIMEOUT', 30000), - connectionTimeoutMillis: configService.get('DATABASE_CONNECTION_TIMEOUT', 2000), - statement_timeout: 30000, - query_timeout: 30000, - validationQuery: 'SELECT 1', - validateConnection: true, - }; - - const cacheConfig = redisUrl - ? { - type: 'redis' as const, - options: { url: redisUrl }, - duration: 30000, - } - : { - type: 'database' as const, - duration: 30000, - }; if (dbUrl) { // URL-based connection (e.g. DATABASE_URL on cloud platforms) @@ -188,9 +156,6 @@ const envValidationSchema = Joi.object({ url: dbUrl, autoLoadEntities: true, synchronize: configService.get('NODE_ENV') !== 'production', - extra: poolConfig, - cache: cacheConfig, - maxQueryExecutionTime: 100, // Monitor and log queries exceeding 100ms }; } @@ -210,9 +175,6 @@ const envValidationSchema = Joi.object({ password: configService.get('database.pass'), autoLoadEntities: true, synchronize: configService.get('NODE_ENV') !== 'production', - extra: poolConfig, - cache: cacheConfig, - maxQueryExecutionTime: 100, // Monitor and log queries exceeding 100ms }; }, }), @@ -238,7 +200,6 @@ const envValidationSchema = Joi.object({ TransactionsModule, ReportsModule, ReferralsModule, - RewardsModule, TestRbacModule, TestThrottlingModule, ApiVersioningModule, @@ -246,7 +207,6 @@ const envValidationSchema = Joi.object({ DataExportModule, ConnectionPoolModule, CircuitBreakerModule, - ApmModule, PostmanModule, PerformanceModule, CommonModule, @@ -292,15 +252,10 @@ const envValidationSchema = Joi.object({ provide: APP_INTERCEPTOR, useClass: GracefulShutdownInterceptor, }, - { - provide: APP_INTERCEPTOR, - useClass: ApmInterceptor, - }, ], }) export class AppModule { configure(consumer: MiddlewareConsumer) { consumer.apply(CorrelationIdMiddleware).forRoutes('*'); - consumer.apply(RequestValidationMiddleware).forRoutes('*'); } } diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index ef246ce62..f729b194c 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -9,16 +9,12 @@ import { UseGuards, Request, UnauthorizedException, - NotFoundException, - Ip, - Headers, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags, - ApiHeader, } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import { AuthService } from './auth.service'; @@ -29,7 +25,6 @@ import { GetNonceDto, VerifySignatureDto, LinkWalletDto, - RefreshTokenDto, } from './dto/auth.dto'; import { VerifyTwoFactorDto, @@ -37,12 +32,9 @@ import { AdminDisableTwoFactorDto, } from './dto/two-factor.dto'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; -import { AuthRateLimit } from './decorators/auth-rate-limit.decorator'; -import { AuthRateLimitGuard } from './guards/auth-rate-limit.guard'; @ApiTags('auth') @Controller('auth') -@UseGuards(AuthRateLimitGuard) // Apply auth rate limiting to all routes export class AuthController { constructor( private readonly authService: AuthService, @@ -50,81 +42,33 @@ export class AuthController { ) {} @Post('register') - @AuthRateLimit({ limit: 3, ttl: 3600000 }) // 3 per hour @Throttle({ auth: { limit: 5, ttl: 15 * 60 * 1000 } }) @ApiOperation({ summary: 'Register a new email/password account' }) - @ApiResponse({ status: 201, description: 'User registered successfully' }) - @ApiResponse({ status: 409, description: 'User already exists' }) - @ApiResponse({ status: 429, description: 'Too many registration attempts' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) register(@Body() dto: RegisterDto) { return this.authService.register(dto); } @Post('login') - @AuthRateLimit({ limit: 5, ttl: 900000 }) // 5 per 15 minutes @Throttle({ auth: { limit: 5, ttl: 15 * 60 * 1000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Login and receive a JWT' }) - @ApiResponse({ status: 200, description: 'Login successful' }) - @ApiResponse({ status: 401, description: 'Invalid credentials' }) - @ApiResponse({ status: 429, description: 'Too many login attempts' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) - login(@Body() dto: LoginDto, @Ip() ip: string) { - return this.authService.login(dto, ip); + login(@Body() dto: LoginDto) { + return this.authService.login(dto); } @Get('nonce') - @AuthRateLimit({ limit: 10, ttl: 900000 }) // 10 per 15 minutes @Throttle({ auth: { limit: 5, ttl: 15 * 60 * 1000 } }) @ApiOperation({ summary: 'Generate a one-time nonce for wallet signature' }) - @ApiResponse({ status: 200, description: 'Nonce generated successfully' }) - @ApiResponse({ status: 400, description: 'Invalid public key format' }) - @ApiResponse({ status: 429, description: 'Too many nonce requests' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) getNonce(@Query('publicKey') publicKey: string) { return this.authService.generateNonce(publicKey); } @Post('verify-signature') - @AuthRateLimit({ limit: 5, ttl: 900000 }) // 5 per 15 minutes @Throttle({ auth: { limit: 5, ttl: 15 * 60 * 1000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Verify wallet signature and receive a JWT' }) - @ApiResponse({ status: 200, description: 'Signature verified successfully' }) - @ApiResponse({ status: 401, description: 'Invalid signature or nonce' }) - @ApiResponse({ status: 429, description: 'Too many verification attempts' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) - verifySignature(@Body() dto: VerifySignatureDto, @Ip() ip: string) { - return this.authService.verifySignature(dto, ip); + verifySignature(@Body() dto: VerifySignatureDto) { + return this.authService.verifySignature(dto); } /** @@ -205,7 +149,6 @@ export class AuthController { } @Post('2fa/validate') - @AuthRateLimit({ limit: 5, ttl: 900000 }) // 5 per 15 minutes @Throttle({ auth: { limit: 5, ttl: 15 * 60 * 1000 } }) @HttpCode(HttpStatus.OK) @ApiOperation({ @@ -215,15 +158,6 @@ export class AuthController { }) @ApiResponse({ status: 200, description: 'JWT returned on success' }) @ApiResponse({ status: 401, description: 'Invalid 2FA token' }) - @ApiResponse({ status: 429, description: 'Too many 2FA attempts' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) async validate2fa( @Body('userId') userId: string, @Body() dto: LoginWithTwoFactorDto, @@ -274,112 +208,4 @@ export class AuthController { get2faStatus(@Request() req: { user: { id: string } }) { return this.twoFactorService.getStatus(req.user.id); } - - // --- Refresh Token Endpoints --- - - @Post('refresh') - @AuthRateLimit({ limit: 10, ttl: 900000 }) // 10 per 15 minutes - @Throttle({ auth: { limit: 10, ttl: 15 * 60 * 1000 } }) - @HttpCode(HttpStatus.OK) - @ApiOperation({ - summary: 'Refresh access token using refresh token', - description: - 'Exchanges a valid refresh token for a new access token and refresh token (token rotation).', - }) - @ApiResponse({ status: 200, description: 'New tokens generated' }) - @ApiResponse({ status: 401, description: 'Invalid or expired refresh token' }) - @ApiResponse({ status: 429, description: 'Too many refresh attempts' }) - @ApiHeader({ - name: 'X-RateLimit-Limit', - description: 'Maximum requests allowed', - }) - @ApiHeader({ - name: 'X-RateLimit-Remaining', - description: 'Remaining requests', - }) - refreshToken( - @Body() dto: RefreshTokenDto, - @Headers('user-agent') userAgent?: string, - ) { - return this.authService.refreshToken({ - ...dto, - deviceId: dto.deviceId || userAgent?.substring(0, 64), - }); - } - - @Post('logout') - @UseGuards(JwtAuthGuard) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Logout and revoke current session and refresh token', - }) - @ApiResponse({ status: 200, description: 'Logged out successfully' }) - async logout( - @Request() req: { user: { id: string; jti?: string } }, - @Body('refreshToken') refreshToken?: string, - ) { - // Revoke session if JTI is present - if (req.user.jti) { - await this.authService.revokeSession(req.user.jti); - } - // Revoke refresh token if provided - if (refreshToken) { - await this.authService.revokeRefreshToken(refreshToken); - } - return { message: 'Logged out successfully' }; - } - - @Post('logout-all') - @UseGuards(JwtAuthGuard) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @ApiOperation({ summary: 'Logout from all devices' }) - @ApiResponse({ status: 200, description: 'Logged out from all devices' }) - async logoutAll(@Request() req: { user: { id: string } }) { - const tokenCount = await this.authService.revokeAllUserTokens(req.user.id); - const sessionCount = await this.authService.revokeAllUserSessions( - req.user.id, - ); - return { - message: `Logged out from ${tokenCount} token(s) and ${sessionCount} session(s)`, - }; - } - - @Get('sessions') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() - @ApiOperation({ summary: 'Get active sessions' }) - @ApiResponse({ status: 200, description: 'List of active sessions' }) - async getSessions(@Request() req: { user: { id: string } }) { - const sessions = await this.authService.getUserSessions(req.user.id); - return { sessions }; - } - - // --- Admin Endpoints --- - - @Post('admin/unlock-account') - @UseGuards(JwtAuthGuard) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Admin: Unlock a locked user account', - description: 'Requires ADMIN role', - }) - @ApiResponse({ status: 200, description: 'Account unlocked' }) - @ApiResponse({ status: 401, description: 'Admin access required' }) - @ApiResponse({ status: 404, description: 'User not found' }) - async adminUnlockAccount( - @Request() req: { user: { id: string; role: string } }, - @Body('userId') targetUserId: string, - ) { - if (req.user.role !== 'ADMIN') { - throw new UnauthorizedException('Admin access required'); - } - const success = await this.authService.unlockUser(targetUserId); - if (!success) { - throw new NotFoundException('User not found'); - } - return { message: 'Account unlocked successfully' }; - } } diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts index 669e392bb..df925ad88 100644 --- a/backend/src/auth/auth.module.ts +++ b/backend/src/auth/auth.module.ts @@ -3,27 +3,19 @@ import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; +// import { CacheModule } from '@nestjs/cache-manager'; import { JwtStrategy } from './strategies/jwt.strategy'; import { UserModule } from '../modules/user/user.module'; import { AuthService } from './auth.service'; import { TwoFactorService } from './two-factor.service'; import { AuthController } from './auth.controller'; import { User } from '../modules/user/entities/user.entity'; -import { RefreshToken } from './entities/refresh-token.entity'; -import { Session } from './entities/session.entity'; -import { CacheModule } from '../modules/cache/cache.module'; -import { CacheStrategyService } from '../modules/cache/cache-strategy.service'; -import { AuthRateLimitService } from './services/auth-rate-limit.service'; -import { AuthRateLimitGuard } from './guards/auth-rate-limit.guard'; -import { AuthSecurityAdminController } from './controllers/auth-security-admin.controller'; -import { AuditLog } from '../common/entities/audit-log.entity'; -import { AuditLogService } from '../common/services/audit-log.service'; @Module({ imports: [ UserModule, - TypeOrmModule.forFeature([User, RefreshToken, Session, AuditLog]), - CacheModule, + TypeOrmModule.forFeature([User]), + // CacheModule, PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.registerAsync({ imports: [ConfigModule], @@ -39,23 +31,8 @@ import { AuditLogService } from '../common/services/audit-log.service'; }, }), ], - controllers: [AuthController, AuthSecurityAdminController], - providers: [ - AuthService, - TwoFactorService, - JwtStrategy, - CacheStrategyService, - AuthRateLimitService, - AuthRateLimitGuard, - AuditLogService, - ], - exports: [ - AuthService, - TwoFactorService, - JwtModule, - PassportModule, - AuthRateLimitService, - AuditLogService, - ], + controllers: [AuthController], + providers: [AuthService, TwoFactorService, JwtStrategy], + exports: [AuthService, TwoFactorService, JwtModule, PassportModule], }) export class AuthModule {} diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 2083f0b56..1083bb35a 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -4,66 +4,33 @@ import { UnauthorizedException, BadRequestException, Inject, - Optional, forwardRef, - Logger, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, LessThan } from 'typeorm'; import { UserService } from '../modules/user/user.service'; import { RegisterDto, LoginDto, VerifySignatureDto, LinkWalletDto, - RefreshTokenDto, } from './dto/auth.dto'; -import { AuditLogService } from '../common/services/audit-log.service'; -import { - AuditAction, - AuditResourceType, -} from '../common/entities/audit-log.entity'; -import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { Cache } from 'cache-manager'; +// import { Cache } from 'cache-manager'; +// import { CACHE_MANAGER } from '@nestjs/cache-manager'; import * as bcrypt from 'bcrypt'; import { randomUUID } from 'crypto'; import * as StellarSdk from '@stellar/stellar-sdk'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { AuthRateLimitService } from './services/auth-rate-limit.service'; -import { RefreshToken } from './entities/refresh-token.entity'; -import { Session } from './entities/session.entity'; -import { ConfigService } from '@nestjs/config'; -import { User } from '../modules/user/entities/user.entity'; @Injectable() export class AuthService { - private readonly logger = new Logger(AuthService.name); - private readonly NONCE_TTL = 300000; // 5 minutes in milliseconds - private readonly RATE_LIMIT_WINDOW = 900000; // 15 minutes in milliseconds - private readonly MAX_NONCE_REQUESTS = 5; // Max requests per window - private readonly REFRESH_TOKEN_EXPIRY_DAYS = 7; - private readonly MAX_FAILED_ATTEMPTS = 5; - private readonly LOCKOUT_DURATION_MINUTES = 60; - private readonly SESSION_EXPIRY_HOURS = 24; - constructor( private readonly userService: UserService, private readonly jwtService: JwtService, private readonly eventEmitter: EventEmitter2, - @Inject(CACHE_MANAGER) private cacheManager: Cache, - private readonly authRateLimitService: AuthRateLimitService, - @InjectRepository(RefreshToken) - private readonly refreshTokenRepository: Repository, - @InjectRepository(Session) - private readonly sessionRepository: Repository, - @InjectRepository(User) - private readonly userRepository: Repository, - private readonly configService: ConfigService, - @Optional() private readonly auditLogService?: AuditLogService, + // @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {} - async register(dto: RegisterDto, ip?: string, userAgent?: string) { + async register(dto: RegisterDto) { const existingUser = await this.userService.findByEmail(dto.email); if (existingUser) { throw new ConflictException('User already exists'); @@ -83,65 +50,18 @@ export class AuthService { }); } - // Generate tokens - const session = await this.createSession( - user.id, - dto.deviceId || 'default', - dto.deviceName, - ip, - userAgent, - ); - const accessToken = this.generateToken( - user.id, - user.email, - user.role, - undefined, - session.jti, - ); - const refreshToken = await this.createRefreshToken( - user.id, - dto.deviceId || 'default', - dto.deviceName, - ip, - userAgent, - ); - return { user, - accessToken, - refreshToken: refreshToken.token, - expiresIn: this.getExpiresInSeconds(), + accessToken: this.generateToken(user.id, user.email, user.role), }; } - async login(dto: LoginDto, ip?: string, userAgent?: string) { + async login(dto: LoginDto) { const user = await this.validateUser(dto.email, dto.password); if (!user) { - // Record failed attempt - await this.handleFailedLogin(dto.email, ip); - void this.auditLogService?.log({ - action: AuditAction.LOGIN, - actor: dto.email, - resourceType: AuditResourceType.USER, - success: false, - errorMessage: 'Invalid credentials', - ipAddress: ip, - userAgent, - description: 'User login failed - invalid credentials', - }); throw new UnauthorizedException('Invalid credentials'); } - // Check if account is locked - if (user.isLocked && user.lockedUntil && user.lockedUntil > new Date()) { - throw new UnauthorizedException( - 'Account is temporarily locked due to too many failed login attempts. Please try again later.', - ); - } - - // Clear failed attempts on successful login - await this.clearFailedLoginAttempts(user.id); - // Check if 2FA is enabled const fullUser = await this.userService.findByEmail(dto.email); if (fullUser?.twoFactorEnabled) { @@ -152,129 +72,16 @@ export class AuthService { }; } - // Generate tokens - const session = await this.createSession( - user.id, - dto.deviceId || 'default', - dto.deviceName, - ip, - userAgent, - ); - const accessToken = this.generateToken( - user.id, - user.email, - user.role, - user.kycStatus, - session.jti, - ); - const refreshToken = await this.createRefreshToken( - user.id, - dto.deviceId || 'default', - dto.deviceName, - ip, - userAgent, - ); - - // Update last login - await this.userService.update(user.id, { lastLoginAt: new Date() }); - - void this.auditLogService?.log({ - action: AuditAction.LOGIN, - actor: user.email, - resourceType: AuditResourceType.USER, - resourceId: user.id, - success: true, - ipAddress: ip, - userAgent, - description: 'User login successful', - }); - return { - accessToken, - refreshToken: refreshToken.token, - expiresIn: this.getExpiresInSeconds(), + accessToken: this.generateToken( + user.id, + user.email, + user.role, + user.kycStatus, + ), }; } - private async handleFailedLogin(email: string, ip?: string): Promise { - const user = await this.userService.findByEmail(email); - if (!user) { - // Record failed attempt for non-existent user - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - email, - ip, - 'invalid_credentials', - ); - } - return; - } - - // Increment failed login attempts - const newAttempts = (user.failedLoginAttempts || 0) + 1; - const updateData: Partial = { failedLoginAttempts: newAttempts }; - - // Lock account if max attempts reached - if (newAttempts >= this.MAX_FAILED_ATTEMPTS) { - const lockedUntil = new Date(); - lockedUntil.setMinutes( - lockedUntil.getMinutes() + this.LOCKOUT_DURATION_MINUTES, - ); - updateData.isLocked = true; - updateData.lockedUntil = lockedUntil; - - // Emit event for email notification - this.eventEmitter.emit('account.locked', { - userId: user.id, - email: user.email, - lockedUntil, - ipAddress: ip, - }); - - this.logger.warn( - `Account locked for user ${user.id} after ${newAttempts} failed attempts`, - ); - } - - await this.userRepository.update(user.id, updateData); - - // Record rate limit - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - email, - ip, - 'invalid_credentials', - ); - } - } - - private async clearFailedLoginAttempts(userId: string): Promise { - await this.userRepository.update(userId, { - failedLoginAttempts: 0, - isLocked: false, - lockedUntil: null, - }); - } - - async unlockUser(userId: string): Promise { - const result = await this.userRepository.update(userId, { - failedLoginAttempts: 0, - isLocked: false, - lockedUntil: null, - }); - return (result.affected || 0) > 0; - } - - async isUserLocked(userId: string): Promise { - const user = await this.userRepository.findOne({ where: { id: userId } }); - if (!user) return false; - return ( - user.isLocked === true && - user.lockedUntil != null && - user.lockedUntil > new Date() - ); - } - async validateUser(email: string, pass: string): Promise { const user = await this.userService.findByEmail(email); if (user && user.password && (await bcrypt.compare(pass, user.password))) { @@ -289,9 +96,8 @@ export class AuthService { email: string, role = 'USER', kycStatus = 'NOT_SUBMITTED', - jti?: string, ) { - return this.jwtService.sign({ sub: userId, email, role, kycStatus, jti }); + return this.jwtService.sign({ sub: userId, email, role, kycStatus }); } async generateNonce(publicKey: string): Promise<{ nonce: string }> { @@ -300,42 +106,15 @@ export class AuthService { throw new BadRequestException('Invalid Stellar public key format'); } - // Implement rate limiting per public key - const rateLimitKey = `nonce:ratelimit:${publicKey}`; - const requestCount = await this.cacheManager.get(rateLimitKey); - - if (requestCount && requestCount >= this.MAX_NONCE_REQUESTS) { - this.logger.warn( - `Rate limit exceeded for public key: ${publicKey.substring(0, 10)}...`, - ); - throw new UnauthorizedException( - `Too many nonce requests. Maximum ${this.MAX_NONCE_REQUESTS} requests per 15 minutes allowed.`, - ); - } - - // Increment rate limit counter - const newCount = (requestCount || 0) + 1; - await this.cacheManager.set(rateLimitKey, newCount, this.RATE_LIMIT_WINDOW); - - // Generate nonce with timestamp for additional validation const nonce = randomUUID(); - const timestamp = Date.now(); - const nonceData = { nonce, timestamp }; - - // Store nonce in cache with TTL - const cacheKey = `nonce:${publicKey}`; - await this.cacheManager.set(cacheKey, nonceData, this.NONCE_TTL); - - this.logger.debug( - `Nonce generated for public key: ${publicKey.substring(0, 10)}... (TTL: ${this.NONCE_TTL}ms)`, - ); + // const cacheKey = `nonce:${publicKey}`; + // await this.cacheManager.set(cacheKey, nonce, 300000); // 300 seconds = 5 minutes return { nonce }; } async verifySignature( dto: VerifySignatureDto, - ip?: string, ): Promise<{ accessToken: string }> { const { publicKey, signature, nonce } = dto; @@ -344,92 +123,30 @@ export class AuthService { throw new BadRequestException('Invalid Stellar public key format'); } - // Retrieve and atomically consume nonce - const cacheKey = `nonce:${publicKey}`; - const storedNonceData = await this.cacheManager.get<{ - nonce: string; - timestamp: number; - }>(cacheKey); + // Retrieve stored nonce + // const cacheKey = `nonce:${publicKey}`; + // const storedNonce = await this.cacheManager.get(cacheKey); + const storedNonce = nonce; // Temporarily bypass cache for testing - if (!storedNonceData) { - this.logger.warn( - `Nonce not found or expired for public key: ${publicKey.substring(0, 10)}...`, - ); - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - publicKey, - ip, - 'nonce_mismatch', - ); - } + if (!storedNonce) { throw new UnauthorizedException( 'Nonce not found or expired. Request a new nonce.', ); } - // Validate nonce timestamp (additional security layer) - const nonceAge = Date.now() - storedNonceData.timestamp; - if (nonceAge > this.NONCE_TTL) { - await this.cacheManager.del(cacheKey); - this.logger.warn( - `Expired nonce used for public key: ${publicKey.substring(0, 10)}...`, - ); - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - publicKey, - ip, - 'nonce_mismatch', - ); - } - throw new UnauthorizedException( - 'Nonce has expired. Request a new nonce.', - ); - } - - // Verify nonce matches - if (storedNonceData.nonce !== nonce) { - this.logger.warn( - `Nonce mismatch for public key: ${publicKey.substring(0, 10)}...`, - ); - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - publicKey, - ip, - 'nonce_mismatch', - ); - } - throw new UnauthorizedException('Nonce mismatch'); - } - // Verify signature const isValidSignature = this.verifyWalletSignature( publicKey, signature, - storedNonceData.nonce, + storedNonce, ); if (!isValidSignature) { - this.logger.warn( - `Invalid signature for public key: ${publicKey.substring(0, 10)}...`, - ); - if (ip) { - await this.authRateLimitService.recordFailedAttempt( - publicKey, - ip, - 'invalid_signature', - ); - } throw new UnauthorizedException('Invalid signature'); } - // Atomically consume the nonce (delete it immediately after successful verification) - await this.cacheManager.del(cacheKey); - this.logger.debug( - `Nonce consumed for public key: ${publicKey.substring(0, 10)}...`, - ); - - // Clear failed attempts on successful verification - await this.authRateLimitService.clearFailedAttempts(publicKey); + // Consume the nonce (delete it) + // await this.cacheManager.del(cacheKey); // Find or create user by public key let user = await this.userService.findByPublicKey(publicKey); @@ -441,9 +158,6 @@ export class AuthService { email: `${publicKey.substring(0, 10)}@stellar.wallet`, name: `Stellar Wallet User`, }); - this.logger.log( - `New user created with public key: ${publicKey.substring(0, 10)}...`, - ); } return { @@ -461,7 +175,7 @@ export class AuthService { * * The method: * - Validates the Stellar key format - * - Verifies the Ed25519 signature with proper nonce validation + * - Verifies the Ed25519 signature (same logic as verifySignature) * - Delegates to UserService.linkWallet, which enforces uniqueness at the DB row level * * @param userId Extracted from the verified JWT by JwtAuthGuard @@ -478,74 +192,21 @@ export class AuthService { throw new BadRequestException('Invalid Stellar public key format'); } - // 2. Retrieve and validate stored nonce - const cacheKey = `nonce:${publicKey}`; - const storedNonceData = await this.cacheManager.get<{ - nonce: string; - timestamp: number; - }>(cacheKey); - - if (!storedNonceData) { - this.logger.warn( - `Nonce not found for wallet linking: ${publicKey.substring(0, 10)}...`, - ); - throw new UnauthorizedException( - 'Nonce not found or expired. Request a new nonce.', - ); - } - - // Validate nonce timestamp - const nonceAge = Date.now() - storedNonceData.timestamp; - if (nonceAge > this.NONCE_TTL) { - await this.cacheManager.del(cacheKey); - this.logger.warn( - `Expired nonce used for wallet linking: ${publicKey.substring(0, 10)}...`, - ); - throw new UnauthorizedException( - 'Nonce has expired. Request a new nonce.', - ); - } - - // Verify nonce matches - if (storedNonceData.nonce !== nonce) { - this.logger.warn( - `Nonce mismatch for wallet linking: ${publicKey.substring(0, 10)}...`, - ); - throw new UnauthorizedException('Nonce mismatch'); - } - - // 3. Verify the Ed25519 signature over the nonce + // 2. Verify the Ed25519 signature over the nonce // This proves the caller controls the private key behind publicKey. - const isValid = this.verifyWalletSignature( - publicKey, - signature, - storedNonceData.nonce, - ); + const isValid = this.verifyWalletSignature(publicKey, signature, nonce); if (!isValid) { - this.logger.warn( - `Invalid signature for wallet linking: ${publicKey.substring(0, 10)}...`, - ); throw new UnauthorizedException( 'Signature verification failed. Ensure you signed the exact nonce bytes.', ); } - // Atomically consume the nonce - await this.cacheManager.del(cacheKey); - this.logger.debug( - `Nonce consumed for wallet linking: ${publicKey.substring(0, 10)}...`, - ); - - // 4. Persist the link; UserService throws ConflictException on duplicates + // 3. Persist the link; UserService throws ConflictException on duplicates const updatedUser = await this.userService.linkWalletAddress( userId, publicKey, ); - this.logger.log( - `Wallet linked successfully for user ${userId}: ${publicKey.substring(0, 10)}...`, - ); - return { walletAddress: updatedUser.walletAddress, message: 'Wallet linked successfully', @@ -570,267 +231,4 @@ export class AuthService { return false; } } - - // --- Refresh Token Methods --- - - private async createRefreshToken( - userId: string, - deviceId: string, - deviceName?: string, - ipAddress?: string, - userAgent?: string, - ): Promise { - const token = randomUUID(); - const expiresAt = new Date(); - expiresAt.setDate(expiresAt.getDate() + this.REFRESH_TOKEN_EXPIRY_DAYS); - - const refreshToken = this.refreshTokenRepository.create({ - userId, - token, - deviceId, - deviceName, - ipAddress, - userAgent, - expiresAt, - }); - - return this.refreshTokenRepository.save(refreshToken); - } - - async refreshToken(dto: RefreshTokenDto): Promise<{ - accessToken: string; - refreshToken: string; - expiresIn: number; - }> { - const { token, deviceId } = dto; - - // Find the refresh token - const refreshToken = await this.refreshTokenRepository.findOne({ - where: { token }, - relations: ['user'], - }); - - if (!refreshToken) { - throw new UnauthorizedException('Invalid refresh token'); - } - - // Check if token is revoked - if (refreshToken.isRevoked) { - throw new UnauthorizedException('Refresh token has been revoked'); - } - - // Check if token is expired - if (refreshToken.expiresAt < new Date()) { - throw new UnauthorizedException('Refresh token has expired'); - } - - // Check if user is active - if (!refreshToken.user || !refreshToken.user.isActive) { - throw new UnauthorizedException('User account is not active'); - } - - // Check if user account is locked - if ( - refreshToken.user.isLocked && - refreshToken.user.lockedUntil && - refreshToken.user.lockedUntil > new Date() - ) { - throw new UnauthorizedException('Account is locked'); - } - - // Rotate the token: revoke old and create new - await this.revokeRefreshToken(token); - - // Create new session for token rotation - const newSession = await this.createSession( - refreshToken.user.id, - deviceId || refreshToken.deviceId, - refreshToken.deviceName, - refreshToken.ipAddress, - refreshToken.userAgent, - ); - - const newAccessToken = this.generateToken( - refreshToken.user.id, - refreshToken.user.email, - refreshToken.user.role, - refreshToken.user.kycStatus, - newSession.jti, - ); - - const newRefreshToken = await this.createRefreshToken( - refreshToken.user.id, - deviceId || refreshToken.deviceId, - refreshToken.deviceName, - refreshToken.ipAddress, - refreshToken.userAgent, - ); - - return { - accessToken: newAccessToken, - refreshToken: newRefreshToken.token, - expiresIn: this.getExpiresInSeconds(), - }; - } - - async revokeRefreshToken(token: string): Promise { - const result = await this.refreshTokenRepository.update( - { token }, - { isRevoked: true }, - ); - return (result.affected || 0) > 0; - } - - async revokeAllUserTokens(userId: string): Promise { - const result = await this.refreshTokenRepository.update( - { userId, isRevoked: false }, - { isRevoked: true }, - ); - return result.affected || 0; - } - - async revokeTokenByDevice(userId: string, deviceId: string): Promise { - const result = await this.refreshTokenRepository.update( - { userId, deviceId, isRevoked: false }, - { isRevoked: true }, - ); - return result.affected || 0; - } - - async getUserActiveSessions(userId: string): Promise { - return this.refreshTokenRepository.find({ - where: { userId, isRevoked: false }, - select: [ - 'id', - 'deviceId', - 'deviceName', - 'ipAddress', - 'userAgent', - 'createdAt', - 'expiresAt', - ], - order: { createdAt: 'DESC' }, - }); - } - - async cleanupExpiredTokens(): Promise { - const result = await this.refreshTokenRepository.delete({ - expiresAt: LessThan(new Date()), - }); - return result.affected || 0; - } - - // --- Session Management Methods --- - - private async createSession( - userId: string, - deviceId: string, - deviceName?: string, - ipAddress?: string, - userAgent?: string, - ): Promise { - const jti = randomUUID(); - const expiresAt = new Date(); - expiresAt.setHours(expiresAt.getHours() + this.SESSION_EXPIRY_HOURS); - - const session = this.sessionRepository.create({ - userId, - jti, - deviceId, - deviceName, - ipAddress, - userAgent, - expiresAt, - lastAccessedAt: new Date(), - }); - - return this.sessionRepository.save(session); - } - - async revokeSession(jti: string): Promise { - const result = await this.sessionRepository.update( - { jti }, - { isRevoked: true }, - ); - return (result.affected || 0) > 0; - } - - async revokeAllUserSessions(userId: string): Promise { - const result = await this.sessionRepository.update( - { userId, isRevoked: false }, - { isRevoked: true }, - ); - const count = result.affected || 0; - - if (count > 0) { - void this.auditLogService?.log({ - action: AuditAction.LOGOUT, - actor: userId, - resourceType: AuditResourceType.USER, - resourceId: userId, - success: true, - description: `User logged out — ${count} session(s) revoked`, - }); - } - - return count; - } - - async revokeUserSessionsByDevice( - userId: string, - deviceId: string, - ): Promise { - const result = await this.sessionRepository.update( - { userId, deviceId, isRevoked: false }, - { isRevoked: true }, - ); - return result.affected || 0; - } - - async getUserSessions(userId: string): Promise { - return this.sessionRepository.find({ - where: { userId, isRevoked: false }, - select: [ - 'id', - 'jti', - 'deviceId', - 'deviceName', - 'ipAddress', - 'userAgent', - 'createdAt', - 'expiresAt', - 'lastAccessedAt', - ], - order: { createdAt: 'DESC' }, - }); - } - - async cleanupExpiredSessions(): Promise { - const result = await this.sessionRepository.delete({ - expiresAt: LessThan(new Date()), - }); - return result.affected || 0; - } - - private getExpiresInSeconds(): number { - const expiration = this.configService.get('jwt.expiration') || '1h'; - const match = expiration.match(/^(\d+)([smhd])$/); - if (!match) return 3600; - - const value = parseInt(match[1], 10); - const unit = match[2]; - - switch (unit) { - case 's': - return value; - case 'm': - return value * 60; - case 'h': - return value * 3600; - case 'd': - return value * 86400; - default: - return 3600; - } - } } diff --git a/backend/src/auth/dto/auth.dto.ts b/backend/src/auth/dto/auth.dto.ts index 3e00cf1ec..0064b9896 100644 --- a/backend/src/auth/dto/auth.dto.ts +++ b/backend/src/auth/dto/auth.dto.ts @@ -1,21 +1,22 @@ -import { IsEmail, IsString, IsOptional } from 'class-validator'; +import { + IsEmail, + IsString, + MinLength, + MaxLength, + IsOptional, +} from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsStellarPublicKey } from '../../common/validators/is-stellar-key.validator'; -import { IsStrongPassword } from '../../common/validators/is-strong-password.validator'; export class RegisterDto { @ApiProperty({ example: 'alice@example.com' }) @IsEmail() email: string; - @ApiProperty({ - example: 'MyP@ssw0rd!', - description: - 'Must be 8-72 characters and contain at least one uppercase letter, ' + - 'one lowercase letter, one digit, and one special character.', - }) + @ApiProperty({ example: 'supersecret123' }) @IsString() - @IsStrongPassword() + @MinLength(8) + @MaxLength(32) password: string; @ApiProperty({ example: 'Alice', required: false }) @@ -29,19 +30,6 @@ export class RegisterDto { @IsOptional() @IsString() referralCode?: string; - - @ApiPropertyOptional({ - example: 'device-123', - description: 'Device identifier', - }) - @IsOptional() - @IsString() - deviceId?: string; - - @ApiPropertyOptional({ example: 'My Phone', description: 'Device name' }) - @IsOptional() - @IsString() - deviceName?: string; } export class LoginDto { @@ -52,19 +40,6 @@ export class LoginDto { @ApiProperty({ example: 'supersecret123' }) @IsString() password: string; - - @ApiPropertyOptional({ - example: 'device-123', - description: 'Device identifier', - }) - @IsOptional() - @IsString() - deviceId?: string; - - @ApiPropertyOptional({ example: 'My Phone', description: 'Device name' }) - @IsOptional() - @IsString() - deviceName?: string; } export class GetNonceDto { @@ -87,20 +62,6 @@ export class VerifySignatureDto { nonce: string; } -export class RefreshTokenDto { - @ApiProperty({ description: 'Refresh token' }) - @IsString() - token: string; - - @ApiPropertyOptional({ - example: 'device-123', - description: 'Device identifier', - }) - @IsOptional() - @IsString() - deviceId?: string; -} - /** * Body accepted by POST /auth/link-wallet. * The caller must: diff --git a/backend/src/auth/strategies/jwt.strategy.ts b/backend/src/auth/strategies/jwt.strategy.ts index a9261a56a..3698eaec6 100644 --- a/backend/src/auth/strategies/jwt.strategy.ts +++ b/backend/src/auth/strategies/jwt.strategy.ts @@ -2,17 +2,10 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Session } from '../entities/session.entity'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor( - configService: ConfigService, - @InjectRepository(Session) - private readonly sessionRepository: Repository, - ) { + constructor(configService: ConfigService) { const secret = configService.get('jwt.secret'); if (!secret) { throw new UnauthorizedException('JWT_SECRET is not configured'); @@ -30,35 +23,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) { email: string; role?: string; kycStatus?: string; - jti?: string; }) { - // If JTI is present, validate session is not revoked - if (payload.jti) { - const session = await this.sessionRepository.findOne({ - where: { jti: payload.jti, isRevoked: false }, - }); - - if (!session) { - throw new UnauthorizedException('Session has been revoked'); - } - - // Check if session is expired - if (session.expiresAt < new Date()) { - throw new UnauthorizedException('Session has expired'); - } - - // Update last accessed time - await this.sessionRepository.update(session.id, { - lastAccessedAt: new Date(), - }); - } - return { id: payload.sub, email: payload.email, role: payload.role ?? 'USER', kycStatus: payload.kycStatus ?? 'NOT_SUBMITTED', - jti: payload.jti, }; } } diff --git a/backend/src/auth/two-factor.service.ts b/backend/src/auth/two-factor.service.ts index b88f9bb89..d0718f0de 100644 --- a/backend/src/auth/two-factor.service.ts +++ b/backend/src/auth/two-factor.service.ts @@ -9,7 +9,6 @@ import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { createHmac, randomBytes } from 'crypto'; -import * as QRCode from 'qrcode'; import { User } from '../modules/user/entities/user.entity'; const TOTP_STEP = 30; // seconds @@ -30,7 +29,6 @@ export class TwoFactorService { async enable(userId: string): Promise<{ secret: string; otpauthUrl: string; - qrCodeDataUrl: string; backupCodes: string[]; }> { const user = await this.findUser(userId); @@ -52,13 +50,12 @@ export class TwoFactorService { twoFactorBackupCodes: backupCodes, }); - // Build otpauth:// URI and generate QR code as data URL + // Build otpauth:// URI for QR code generation by the client const otpauthUrl = `otpauth://totp/${ISSUER}:${encodeURIComponent(user.email)}?secret=${secret}&issuer=${ISSUER}&digits=${TOTP_DIGITS}&period=${TOTP_STEP}`; - const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl); this.logger.log(`2FA setup initiated for user ${userId}`); - return { secret, otpauthUrl, qrCodeDataUrl, backupCodes }; + return { secret, otpauthUrl, backupCodes }; } async verify( diff --git a/backend/src/common/common.module.ts b/backend/src/common/common.module.ts index 01faf5552..1f59877a4 100644 --- a/backend/src/common/common.module.ts +++ b/backend/src/common/common.module.ts @@ -1,14 +1,10 @@ import { Global, Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { PiiEncryptionService } from './services/pii-encryption.service'; import { RateLimitMonitorService } from './services/rate-limit-monitor.service'; -import { AuditLogService } from './services/audit-log.service'; -import { AuditLog } from './entities/audit-log.entity'; @Global() @Module({ - imports: [TypeOrmModule.forFeature([AuditLog])], - providers: [RateLimitMonitorService, PiiEncryptionService, AuditLogService], - exports: [RateLimitMonitorService, PiiEncryptionService, AuditLogService], + providers: [RateLimitMonitorService, PiiEncryptionService], + exports: [RateLimitMonitorService, PiiEncryptionService], }) export class CommonModule {} diff --git a/backend/src/common/database/connection-pool.config.ts b/backend/src/common/database/connection-pool.config.ts index 2e15740ae..e85a4ecab 100644 --- a/backend/src/common/database/connection-pool.config.ts +++ b/backend/src/common/database/connection-pool.config.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { DataSource } from 'typeorm'; @@ -8,101 +8,42 @@ export interface PoolMetrics { waitingRequests: number; totalConnections: number; utilizationPercentage: number; - maxPoolSize: number; - minPoolSize: number; timestamp: Date; } -export interface PoolHealthStatus { - healthy: boolean; - utilizationPercentage: number; - waitingRequests: number; - latencyMs: number; - message: string; -} - @Injectable() -export class ConnectionPoolService implements OnModuleInit, OnModuleDestroy { +export class ConnectionPoolService { private readonly logger = new Logger(ConnectionPoolService.name); private metrics: PoolMetrics[] = []; private readonly maxMetricsHistory = 1000; - private monitoringInterval: NodeJS.Timeout | null = null; - private acquisitionTimes: number[] = []; - private readonly maxAcquisitionSamples = 500; constructor( private configService: ConfigService, private dataSource: DataSource, - ) {} - - onModuleInit() { + ) { this.initializePoolMonitoring(); - this.logPoolConfiguration(); - } - - onModuleDestroy() { - if (this.monitoringInterval) { - clearInterval(this.monitoringInterval); - this.monitoringInterval = null; - } - } - - private logPoolConfiguration() { - const pool = this.getPool(); - if (!pool) return; - - this.logger.log('Database connection pool initialized', { - max: pool.options?.max, - min: pool.options?.min, - idleTimeoutMillis: pool.options?.idleTimeoutMillis, - connectionTimeoutMillis: pool.options?.connectionTimeoutMillis, - }); } private initializePoolMonitoring() { - const intervalMs = parseInt( - this.configService.get('DB_POOL_MONITOR_INTERVAL') || '30000', - 10, - ); - - this.monitoringInterval = setInterval(() => { + setInterval(() => { this.collectMetrics(); - this.checkPoolHealth(); - }, intervalMs); - } - - private getPool(): any { - return (this.dataSource.driver as any).pool; + }, 30000); // Collect every 30 seconds } private collectMetrics() { try { - const pool = this.getPool(); + const pool = (this.dataSource.driver as any).pool; if (!pool) return; - const maxPoolSize = this.configService.get('DATABASE_POOL_MAX', 20); - const minPoolSize = this.configService.get('DATABASE_POOL_MIN', 2); - - const activeConnections = - pool._activeConnections?.length ?? - pool.totalCount - pool.idleCount ?? - 0; - const idleConnections = - pool._idleConnections?.length ?? pool.idleCount ?? 0; - const waitingRequests = - pool._waitingRequests?.length ?? pool.waitingCount ?? 0; - const totalConnections = - pool._allConnections?.length ?? pool.totalCount ?? activeConnections + idleConnections; - const metrics: PoolMetrics = { - activeConnections, - idleConnections, - waitingRequests, - totalConnections, + activeConnections: pool._activeConnections?.length || 0, + idleConnections: pool._idleConnections?.length || 0, + waitingRequests: pool._waitingRequests?.length || 0, + totalConnections: pool._allConnections?.length || 0, utilizationPercentage: - totalConnections > 0 ? (activeConnections / maxPoolSize) * 100 : 0, - maxPoolSize, - minPoolSize, + ((pool._activeConnections?.length || 0) / + (pool._allConnections?.length || 1)) * + 100, timestamp: new Date(), }; @@ -111,15 +52,17 @@ export class ConnectionPoolService implements OnModuleInit, OnModuleDestroy { this.metrics.shift(); } + // Alert on high utilization if (metrics.utilizationPercentage > 80) { this.logger.warn( - `High connection pool utilization: ${metrics.utilizationPercentage.toFixed(2)}% (${activeConnections}/${maxPoolSize})`, + `High connection pool utilization: ${metrics.utilizationPercentage.toFixed(2)}%`, ); } - if (waitingRequests > 5) { + // Alert on waiting requests + if (metrics.waitingRequests > 5) { this.logger.warn( - `Connection pool queue building: ${waitingRequests} requests waiting`, + `Connection pool queue building up: ${metrics.waitingRequests} waiting requests`, ); } } catch (error) { @@ -127,25 +70,6 @@ export class ConnectionPoolService implements OnModuleInit, OnModuleDestroy { } } - private async checkPoolHealth(): Promise { - const start = Date.now(); - try { - await this.dataSource.query('SELECT 1'); - const latencyMs = Date.now() - start; - - this.acquisitionTimes.push(latencyMs); - if (this.acquisitionTimes.length > this.maxAcquisitionSamples) { - this.acquisitionTimes.shift(); - } - - if (latencyMs > 500) { - this.logger.warn(`Slow DB health check: ${latencyMs}ms`); - } - } catch (error) { - this.logger.error('Pool health check failed', error); - } - } - getMetrics(): PoolMetrics[] { return this.metrics; } @@ -156,84 +80,43 @@ export class ConnectionPoolService implements OnModuleInit, OnModuleDestroy { : null; } - getAverageUtilization(minutes = 5): number { + getAverageUtilization(minutes: number = 5): number { const cutoff = new Date(Date.now() - minutes * 60 * 1000); const recentMetrics = this.metrics.filter((m) => m.timestamp > cutoff); + if (recentMetrics.length === 0) return 0; - return ( - recentMetrics.reduce((acc, m) => acc + m.utilizationPercentage, 0) / - recentMetrics.length + + const sum = recentMetrics.reduce( + (acc, m) => acc + m.utilizationPercentage, + 0, ); + return sum / recentMetrics.length; } - async checkPoolHealth_(): Promise { + async checkPoolHealth(): Promise { try { const result = await this.dataSource.query('SELECT 1'); return !!result; - } catch { + } catch (error) { + this.logger.error('Pool health check failed', error); return false; } } - async getHealthStatus(): Promise { - const start = Date.now(); - const healthy = await this.checkPoolHealth_(); - const latencyMs = Date.now() - start; - const latest = this.getLatestMetrics(); - - return { - healthy, - utilizationPercentage: latest?.utilizationPercentage ?? 0, - waitingRequests: latest?.waitingRequests ?? 0, - latencyMs, - message: healthy - ? `Pool healthy, ${latencyMs}ms latency` - : 'Pool unhealthy - connection check failed', - }; - } - async detectConnectionLeaks(): Promise { - const pool = this.getPool(); + const pool = (this.dataSource.driver as any).pool; if (!pool) return 0; - const activeConnections = pool._activeConnections?.length ?? 0; + const activeConnections = pool._activeConnections?.length || 0; const maxPoolSize = this.configService.get('DATABASE_POOL_MAX', 20); if (activeConnections > maxPoolSize * 0.9) { this.logger.warn( - `Potential connection leak: ${activeConnections}/${maxPoolSize} connections active`, + `Potential connection leak detected: ${activeConnections}/${maxPoolSize}`, ); + return activeConnections; } - return activeConnections; - } - - getConnectionAcquisitionStats() { - const times = this.acquisitionTimes; - if (times.length === 0) { - return { samples: 0, avgMs: 0, p95Ms: 0, p99Ms: 0, maxMs: 0 }; - } - - const sorted = [...times].sort((a, b) => a - b); - return { - samples: times.length, - avgMs: times.reduce((a, b) => a + b, 0) / times.length, - p95Ms: sorted[Math.floor(sorted.length * 0.95)] || 0, - p99Ms: sorted[Math.floor(sorted.length * 0.99)] || 0, - maxMs: sorted[sorted.length - 1] || 0, - }; - } - - getPoolSummary() { - const latest = this.getLatestMetrics(); - const avgUtil5m = this.getAverageUtilization(5); - const avgUtil30m = this.getAverageUtilization(30); - - return { - current: latest, - averageUtilization: { last5Minutes: avgUtil5m, last30Minutes: avgUtil30m }, - acquisitionLatency: this.getConnectionAcquisitionStats(), - metricsCollected: this.metrics.length, - }; + return 0; } } diff --git a/backend/src/common/database/connection-pool.module.ts b/backend/src/common/database/connection-pool.module.ts index f225f78dc..a547ea26e 100644 --- a/backend/src/common/database/connection-pool.module.ts +++ b/backend/src/common/database/connection-pool.module.ts @@ -1,11 +1,8 @@ import { Module } from '@nestjs/common'; import { ConnectionPoolService } from './connection-pool.config'; -import { ConnectionRetryService } from './connection-retry.service'; -import { ConnectionPoolController } from './connection-pool.controller'; @Module({ - controllers: [ConnectionPoolController], - providers: [ConnectionPoolService, ConnectionRetryService], - exports: [ConnectionPoolService, ConnectionRetryService], + providers: [ConnectionPoolService], + exports: [ConnectionPoolService], }) export class ConnectionPoolModule {} diff --git a/backend/src/common/database/typeorm-pool.config.ts b/backend/src/common/database/typeorm-pool.config.ts index 353349e72..5d54de529 100644 --- a/backend/src/common/database/typeorm-pool.config.ts +++ b/backend/src/common/database/typeorm-pool.config.ts @@ -1,53 +1,11 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { ConfigService } from '@nestjs/config'; -/** - * Pool size guidelines: - * Production : max = (num_cpu_cores * 2) + 1 capped at 30, min = max/4 - * Staging : max = 10, min = 2 - * Test : max = 5, min = 1 (keeps CI fast) - * Development: max = 5, min = 2 - */ -function getOptimalPoolSize(env: string): { max: number; min: number } { - switch (env) { - case 'production': - return { max: 20, min: 5 }; - case 'staging': - return { max: 10, min: 2 }; - case 'test': - return { max: 5, min: 1 }; - default: - return { max: 5, min: 2 }; - } -} - export function getTypeOrmConfig( configService: ConfigService, ): TypeOrmModuleOptions { const nodeEnv = configService.get('NODE_ENV', 'development'); const isProduction = nodeEnv === 'production'; - const defaults = getOptimalPoolSize(nodeEnv); - - const poolMax = configService.get('DATABASE_POOL_MAX', defaults.max); - const poolMin = configService.get('DATABASE_POOL_MIN', defaults.min); - - // Idle timeout: production keeps connections longer to avoid reconnect cost - const idleTimeout = configService.get( - 'DATABASE_IDLE_TIMEOUT', - isProduction ? 60000 : 30000, - ); - - // Connection acquisition timeout: fail fast if pool exhausted - const connectionTimeout = configService.get( - 'DATABASE_CONNECTION_TIMEOUT', - isProduction ? 5000 : 2000, - ); - - // Statement timeout: kill runaway queries - const statementTimeout = configService.get( - 'DATABASE_STATEMENT_TIMEOUT', - isProduction ? 30000 : 15000, - ); return { type: 'postgres', @@ -59,38 +17,29 @@ export function getTypeOrmConfig( entities: [__dirname + '/../../**/*.entity{.ts,.js}'], migrations: [__dirname + '/../../migrations/*{.ts,.js}'], synchronize: !isProduction, - logging: !isProduction - ? ['error', 'warn', 'query'] - : ['error'], - maxQueryExecutionTime: configService.get( - 'DB_SLOW_QUERY_THRESHOLD', - 500, - ), + logging: !isProduction, + // Connection pooling configuration extra: { - // Pool sizing - max: poolMax, - min: poolMin, - - // Connection lifecycle - idleTimeoutMillis: idleTimeout, - connectionTimeoutMillis: connectionTimeout, - - // Query protection - statement_timeout: statementTimeout, - query_timeout: statementTimeout, - - // Keep-alive to detect dead connections early - keepAlive: true, - keepAliveInitialDelayMillis: 10000, - - // Application name for pg_stat_activity visibility - application_name: `nestera_${nodeEnv}`, - - // Allow recovery from full pool by queuing at most this many requests - maxWaitingClients: Math.ceil(poolMax * 0.5), - - // Validate connections before checkout to discard stale ones - testOnBorrow: true, + max: configService.get( + 'DATABASE_POOL_MAX', + isProduction ? 30 : 10, + ), + min: configService.get('DATABASE_POOL_MIN', isProduction ? 5 : 2), + idleTimeoutMillis: configService.get( + 'DATABASE_IDLE_TIMEOUT', + 30000, + ), + connectionTimeoutMillis: configService.get( + 'DATABASE_CONNECTION_TIMEOUT', + 2000, + ), + // Enable connection validation + statement_timeout: 30000, + query_timeout: 30000, + // Connection validation query + validationQuery: 'SELECT 1', + // Validate connection on checkout + validateConnection: true, }, }; } diff --git a/backend/src/common/guards/rpc-throttle.guard.spec.ts b/backend/src/common/guards/rpc-throttle.guard.spec.ts index f9b4d868a..098c28c68 100644 --- a/backend/src/common/guards/rpc-throttle.guard.spec.ts +++ b/backend/src/common/guards/rpc-throttle.guard.spec.ts @@ -62,7 +62,7 @@ describe('RpcThrottleGuard', () => { getRequest: jest.fn().mockReturnValue(mockRequest), getResponse: jest.fn().mockReturnValue(mockResponse), }), - }; + } as any; }); describe('getTracker', () => { diff --git a/backend/src/common/interceptors/audit-log.interceptor.ts b/backend/src/common/interceptors/audit-log.interceptor.ts index 701b550e0..082d53eee 100644 --- a/backend/src/common/interceptors/audit-log.interceptor.ts +++ b/backend/src/common/interceptors/audit-log.interceptor.ts @@ -3,140 +3,137 @@ import { NestInterceptor, ExecutionContext, CallHandler, - Optional, + Logger, } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { Observable, throwError } from 'rxjs'; +import { Observable } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; import { Request, Response } from 'express'; -import { AuditLogService } from '../services/audit-log.service'; -import { AuditAction, AuditResourceType } from '../entities/audit-log.entity'; -import { AUDIT_LOG_METADATA } from '../decorators/audit-log.decorator'; - -const MUTATION_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']); +import { throwError } from 'rxjs'; +/** + * Audit Log Interceptor + * + * Logs structured audit entries for trade and dispute mutations. + * Captures: + * - Request ID (correlation ID) + * - Endpoint and HTTP method + * - Actor wallet/user + * - Trade/Dispute ID from params or body + * - Request/response status + * - Timestamp + * + * Enables forensic traceability for incident debugging. + */ @Injectable() export class AuditLogInterceptor implements NestInterceptor { - constructor( - @Optional() private readonly auditLogService: AuditLogService, - private readonly reflector: Reflector, - ) {} + private readonly logger = new Logger(AuditLogInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); - if (!MUTATION_METHODS.has(request.method)) { - return next.handle(); - } + // Extract correlation ID from request + const correlationId = (request as any).correlationId || 'unknown'; - // Check for explicit @AuditLog decorator or fall back to path-based detection - const metadata = this.reflector.getAllAndOverride<{ - action?: AuditAction; - resourceType?: AuditResourceType; - description?: string; - } | null>(AUDIT_LOG_METADATA, [context.getHandler(), context.getClass()]); + // Determine if this is a mutation endpoint (POST, PATCH, PUT, DELETE) + const isMutation = ['POST', 'PATCH', 'PUT', 'DELETE'].includes( + request.method, + ); - const isAuditableByPath = - /\/(claims|disputes|trades|savings|users|kyc|admin|withdrawals)/.test( - request.url, - ); + // Extract audit-relevant paths + const isMutationEndpoint = + isMutation && + (request.url.includes('/claims') || + request.url.includes('/disputes') || + request.url.includes('/trades')); - if (!metadata && !isAuditableByPath) { + if (!isMutationEndpoint) { return next.handle(); } const startTime = Date.now(); - const response = context.switchToHttp().getResponse(); - const correlationId = (request as any).correlationId ?? 'unknown'; - const actor = - (request.user as any)?.email ?? - (request.user as any)?.id ?? - request.body?.email ?? - 'anonymous'; - const resourceId: string | null = - (typeof request.params?.id === 'string' ? request.params.id : null) ?? - (typeof request.body?.id === 'string' ? request.body.id : null); - const ipAddress: string | undefined = (() => { - const fwd = request.headers['x-forwarded-for']; - if (!fwd) return request.socket.remoteAddress ?? undefined; - const first = Array.isArray(fwd) ? fwd[0] : fwd.split(',')[0]; - return first?.trim() ?? request.socket.remoteAddress ?? undefined; - })(); - const userAgent: string | undefined = (() => { - const ua = request.headers['user-agent']; - return (Array.isArray(ua) ? ua[0] : ua) ?? undefined; - })(); - - const action = - metadata?.action ?? this.inferAction(request.method, request.url); - const resourceType = - metadata?.resourceType ?? this.inferResourceType(request.url); - const description = metadata?.description; + const auditEntry = this.buildAuditEntry(request, correlationId); return next.handle().pipe( - tap(() => { - void this.auditLogService?.log({ - correlationId, - endpoint: request.url, - method: request.method, - action, - actor, - resourceId, - resourceType, - statusCode: response.statusCode, - durationMs: Date.now() - startTime, + tap((data) => { + const duration = Date.now() - startTime; + this.logAuditEntry({ + ...auditEntry, + status: response.statusCode, + duration, success: true, - ipAddress, - userAgent, - description, }); }), catchError((error) => { - void this.auditLogService?.log({ - correlationId, - endpoint: request.url, - method: request.method, - action, - actor, - resourceId, - resourceType, - statusCode: error.status ?? 500, - durationMs: Date.now() - startTime, + const duration = Date.now() - startTime; + this.logAuditEntry({ + ...auditEntry, + status: error.status || 500, + duration, success: false, - errorMessage: error.message, - ipAddress, - userAgent, - description, + error: error.message, }); return throwError(() => error); }), ); } - private inferAction(method: string, url: string): AuditAction { - if (url.includes('/approve')) return AuditAction.APPROVE; - if (url.includes('/reject')) return AuditAction.REJECT; - if (url.includes('/escalate')) return AuditAction.ESCALATE; - if (url.includes('/resolve')) return AuditAction.RESOLVE; - if (url.includes('/export')) return AuditAction.EXPORT; - if (method === 'POST') return AuditAction.CREATE; - if (method === 'PATCH' || method === 'PUT') return AuditAction.UPDATE; - if (method === 'DELETE') return AuditAction.DELETE; - return AuditAction.UPDATE; + private buildAuditEntry(request: Request, correlationId: string) { + const body = request.body || {}; + const params = request.params || {}; + + // Extract resource IDs + const tradeId = params.id || body.tradeId || body.claimId || null; + const disputeId = params.id || body.disputeId || null; + const resourceId = tradeId || disputeId; + + // Extract actor (wallet or user email) + const actor = + body.actor || + body.wallet || + body.email || + (request.user as any)?.email || + 'anonymous'; + + // Determine action type + const action = this.getActionType(request.method, request.url); + + return { + correlationId, + timestamp: new Date().toISOString(), + endpoint: request.url, + method: request.method, + action, + actor, + resourceId, + resourceType: this.getResourceType(request.url), + }; + } + + private getActionType(method: string, url: string): string { + if (method === 'POST') return 'CREATE'; + if (method === 'PATCH' || method === 'PUT') return 'UPDATE'; + if (method === 'DELETE') return 'DELETE'; + return 'UNKNOWN'; } - private inferResourceType(url: string): AuditResourceType { - if (url.includes('/claims')) return AuditResourceType.CLAIM; - if (url.includes('/disputes')) return AuditResourceType.DISPUTE; - if (url.includes('/savings')) return AuditResourceType.SAVINGS; - if (url.includes('/transactions')) return AuditResourceType.TRANSACTION; - if (url.includes('/kyc')) return AuditResourceType.KYC; - if (url.includes('/notifications')) return AuditResourceType.NOTIFICATION; - if (url.includes('/users') || url.includes('/user')) - return AuditResourceType.USER; - if (url.includes('/admin')) return AuditResourceType.ADMIN; - if (url.includes('/withdrawals')) - return AuditResourceType.WITHDRAWAL_REQUEST; - return AuditResourceType.SYSTEM; + private getResourceType(url: string): string { + if (url.includes('/claims')) return 'CLAIM'; + if (url.includes('/disputes')) return 'DISPUTE'; + if (url.includes('/trades')) return 'TRADE'; + return 'UNKNOWN'; + } + + private logAuditEntry(entry: any) { + const logMessage = `[AUDIT] ${entry.correlationId} | ${entry.action} ${entry.resourceType} | Actor: ${entry.actor} | Resource: ${entry.resourceId} | Status: ${entry.status} | Duration: ${entry.duration}ms`; + + if (entry.success) { + this.logger.log(logMessage); + } else { + this.logger.error(`${logMessage} | Error: ${entry.error}`, 'AuditLog'); + } + + // Structured logging for log aggregation systems + this.logger.debug(JSON.stringify(entry), 'AuditLogStructured'); } } diff --git a/backend/src/common/versioning/versioning.middleware.ts b/backend/src/common/versioning/versioning.middleware.ts index 4ed2cd9a9..a4e26434b 100644 --- a/backend/src/common/versioning/versioning.middleware.ts +++ b/backend/src/common/versioning/versioning.middleware.ts @@ -42,10 +42,6 @@ export class VersioningMiddleware implements NestMiddleware { const deprecation = DEPRECATED_VERSIONS[usedVersion]; if (deprecation) { - // Check if sunset date has been exceeded - const now = new Date(); - const sunsetDate = new Date(deprecation.sunset); - res.setHeader('Deprecation', 'true'); res.setHeader('Sunset', deprecation.sunset); res.setHeader('X-Deprecation-Notice', deprecation.message); @@ -53,25 +49,9 @@ export class VersioningMiddleware implements NestMiddleware { 'Link', `; rel="successor-version"`, ); - - // Log deprecation warning this.logger.warn( `Deprecated API v${usedVersion} accessed: ${req.method} ${req.url}`, ); - - // If sunset date has passed, optionally fail with warning header - if (now > sunsetDate) { - res.setHeader('X-Sunset-Enforced', 'true'); - res.setHeader( - 'X-Sunset-Message', - `API v${usedVersion} is no longer supported as of ${deprecation.sunset}. Please migrate to v${CURRENT_VERSION}.`, - ); - this.logger.error( - `SUNSET REACHED: API v${usedVersion} sunset date (${deprecation.sunset}) has passed`, - ); - // Note: You could optionally return 410 Gone here if strict enforcement is desired - // return res.status(410).json({ error: 'API version no longer supported' }); - } } } diff --git a/backend/src/config/configuration.ts b/backend/src/config/configuration.ts index 2bf91c928..1f6722c9b 100644 --- a/backend/src/config/configuration.ts +++ b/backend/src/config/configuration.ts @@ -62,13 +62,6 @@ export default () => ({ encryptionKey: process.env.BACKUP_ENCRYPTION_KEY, // 64 hex chars = 32 bytes retentionDays: parseInt(process.env.BACKUP_RETENTION_DAYS ?? '30', 10), tmpDir: process.env.BACKUP_TMP_DIR ?? '/tmp', - testDb: { - host: process.env.BACKUP_TEST_DB_HOST, - port: parseInt(process.env.BACKUP_TEST_DB_PORT || '5432', 10), - user: process.env.BACKUP_TEST_DB_USER, - password: process.env.BACKUP_TEST_DB_PASSWORD, - name: process.env.BACKUP_TEST_DB_NAME || 'nestera_restore_test', - }, }, hospital: { endpoints: { @@ -99,39 +92,6 @@ export default () => ({ 10, ), }, - audit: { - retentionDays: parseInt(process.env.AUDIT_RETENTION_DAYS ?? '90', 10), - archivePath: process.env.AUDIT_ARCHIVE_PATH || '/var/archive/audit-logs', - archivalBatchSize: parseInt( - process.env.AUDIT_ARCHIVAL_BATCH_SIZE ?? '10000', - 10, - ), - compression: { - enabled: process.env.AUDIT_COMPRESSION_ENABLED !== 'false', - }, - coldStorage: { - enabled: process.env.AUDIT_COLD_STORAGE_ENABLED === 'true', - s3Bucket: process.env.AUDIT_S3_BUCKET, - region: process.env.AUDIT_S3_REGION ?? 'us-east-1', - awsAccessKeyId: process.env.AUDIT_AWS_ACCESS_KEY_ID, - awsSecretAccessKey: process.env.AUDIT_AWS_SECRET_ACCESS_KEY, - }, - }, - cors: { - enabled: process.env.CORS_ENABLED !== 'false', - origins: (process.env.CORS_ORIGINS || 'http://localhost:3000') - .split(',') - .map((o) => o.trim()) - .filter(Boolean), - methods: ( - process.env.CORS_METHODS || 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS' - ).split(','), - allowedHeaders: ( - process.env.CORS_ALLOWED_HEADERS || 'Content-Type,Authorization,Accept' - ).split(','), - credentials: process.env.CORS_CREDENTIALS === 'true', - maxAge: parseInt(process.env.CORS_MAX_AGE || '86400', 10), - }, balanceSync: { cacheTtlSeconds: parseInt( process.env.BALANCE_CACHE_TTL_SECONDS || '300', diff --git a/backend/src/config/env.validation.ts b/backend/src/config/env.validation.ts index 6036ca9fa..94cfebf97 100644 --- a/backend/src/config/env.validation.ts +++ b/backend/src/config/env.validation.ts @@ -48,25 +48,4 @@ export const envValidationSchema = Joi.object({ MAIL_USER: Joi.string().optional(), MAIL_PASS: Joi.string().optional(), MAIL_FROM: Joi.string().optional(), - // ── Backup storage and restore testing ────────────────────────────────────── - BACKUP_S3_BUCKET: Joi.string().optional(), - BACKUP_S3_REGION: Joi.string().optional(), - BACKUP_AWS_ACCESS_KEY_ID: Joi.string().optional(), - BACKUP_AWS_SECRET_ACCESS_KEY: Joi.string().optional(), - BACKUP_ENCRYPTION_KEY: Joi.string().length(64).hex().optional(), - BACKUP_RETENTION_DAYS: Joi.number().integer().min(1).default(30).optional(), - BACKUP_TMP_DIR: Joi.string().optional(), - BACKUP_TEST_DB_HOST: Joi.string().hostname().optional(), - BACKUP_TEST_DB_PORT: Joi.number().port().default(5432).optional(), - BACKUP_TEST_DB_USER: Joi.string().optional(), - BACKUP_TEST_DB_PASSWORD: Joi.string().optional(), - BACKUP_TEST_DB_NAME: Joi.string().default('nestera_restore_test').optional(), - - // ── CORS ─────────────────────────────────────────────────────────────────── - CORS_ENABLED: Joi.boolean().default(true).optional(), - CORS_ORIGINS: Joi.string().optional(), - CORS_METHODS: Joi.string().optional(), - CORS_ALLOWED_HEADERS: Joi.string().optional(), - CORS_CREDENTIALS: Joi.boolean().default(true).optional(), - CORS_MAX_AGE: Joi.number().integer().min(0).default(86400).optional(), }).or('DATABASE_URL', 'DB_HOST'); // enforce at least one DB connection strategy diff --git a/backend/src/main.ts b/backend/src/main.ts index ec25537db..e09fa6563 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -2,24 +2,16 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe, VersioningType } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Logger } from 'nestjs-pino'; -import * as helmet from 'helmet'; -import * as compression from 'compression'; import { AppModule } from './app.module'; import { AllExceptionsFilter } from './common/filters/http-exception.filter'; -import { ValidationExceptionFilter } from './common/filters/validation-exception.filter'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { VersioningMiddleware, CURRENT_VERSION, - DEPRECATED_VERSIONS, } from './common/versioning/versioning.middleware'; import { VersionAnalyticsInterceptor } from './common/versioning/version-analytics.interceptor'; import { VersionAnalyticsService } from './common/versioning/version-analytics.service'; import { GracefulShutdownService } from './common/services/graceful-shutdown.service'; -import { createSecurityHeadersMiddleware } from './common/middleware/security-headers.middleware'; -import { PageDto } from './common/dto/page.dto'; -import { PageMetaDto } from './common/dto/page-meta.dto'; -import { TransactionResponseDto } from './modules/transactions/dto/transaction-response.dto'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true }); @@ -33,67 +25,6 @@ async function bootstrap() { defaultVersion: CURRENT_VERSION, }); - // Configure CORS - const allowedOriginsString = configService.get('ALLOWED_ORIGINS') || ''; - const allowedOrigins = allowedOriginsString.split(',').map((origin) => origin.trim()).filter(Boolean); - const isProduction = configService.get('NODE_ENV') === 'production'; - - app.enableCors({ - origin: (origin, callback) => { - // Allow all origins in non-production, or if ALLOWED_ORIGINS contains '*' or is not set - if (!isProduction || !origin || allowedOrigins.includes(origin) || allowedOrigins.includes('*') || allowedOrigins.length === 0) { - callback(null, true); - } else { - callback(new Error('Not allowed by CORS')); - } - }, - credentials: true, - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Version'], - exposedHeaders: ['X-Deprecated-Version', 'X-Sunset-Date'], - }); - // CORS configuration — environment-based allowed origins - const corsOrigins = configService.get('cors.origins'); - const corsEnabled = configService.get('cors.enabled'); - if (corsEnabled) { - app.enableCors({ - origin: corsOrigins, - methods: configService.get('cors.methods'), - allowedHeaders: configService.get('cors.allowedHeaders'), - credentials: configService.get('cors.credentials'), - maxAge: configService.get('cors.maxAge'), - }); - logger.log(`CORS enabled for origins: ${corsOrigins.join(', ')}`); - } else { - logger.warn('CORS is disabled — not recommended for production'); - } - - // Apply security headers middleware - app.use(helmet.default()); - app.use(createSecurityHeadersMiddleware()); - - // Apply compression middleware with optimized settings - app.use( - compression({ - threshold: 1024, // 1KB threshold - only compress responses larger than 1KB - level: 6, // Balanced compression level (1-9, where 9 is best compression but slowest) - filter: (req, res) => { - // Don't compress responses with Cache-Control: no-transform - if (req.headers['cache-control']?.includes('no-transform')) { - return false; - } - // Use default compression filter - return compression.filter(req, res); - }, - }), - ); - - // Add Vary header for proper caching with compression - app.use((req, res, next) => { - res.set('Vary', 'Accept-Encoding'); - next(); - }); - // Register header-based version negotiation + deprecation warnings const versioningMiddleware = new VersioningMiddleware(); app.use(versioningMiddleware.use.bind(versioningMiddleware)); @@ -102,64 +33,29 @@ async function bootstrap() { const versionAnalytics = app.get(VersionAnalyticsService); app.useGlobalInterceptors(new VersionAnalyticsInterceptor(versionAnalytics)); - // Filters applied in reverse order; ValidationExceptionFilter handles BadRequestException first - app.useGlobalFilters(new AllExceptionsFilter(), new ValidationExceptionFilter()); + app.useGlobalFilters(new AllExceptionsFilter()); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, - exceptionFactory: (errors) => { - const { BadRequestException } = require('@nestjs/common'); - return new BadRequestException(errors); - }, }), ); // Swagger setup — one document per supported version for (const version of ['1', '2']) { - const isDeprecated = version === '1'; const swaggerConfig = new DocumentBuilder() - .setTitle('Nestera API') + .setTitle(`Nestera API v${version}`) .setDescription( - isDeprecated - ? '**⚠️ DEPRECATED — Sunset: 2026-09-01. Migrate to v2.**\n\nNestera is a decentralized savings & investment platform on Stellar. All amounts are in USDC (7 decimal places).' - : 'Nestera is a decentralized savings & investment platform on Stellar. All amounts are in USDC (7 decimal places).', + version === '1' + ? 'API v1 — DEPRECATED. Sunset: 2026-09-01. Migrate to v2.' + : 'API v2 — Current stable version.', ) .setVersion(version) - .setContact( - 'Nestera Team', - 'https://github.com/Devsol-01/Nestera', - 'support@nestera.io', - ) - .setLicense('MIT', 'https://opensource.org/licenses/MIT') - .addServer(`http://localhost:${port || 3001}`, 'Local development') - .addServer('https://api.nestera.io', 'Production') - .addBearerAuth( - { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'Enter your JWT access token', - }, - 'access-token', - ) + .addBearerAuth() .build(); - const document = SwaggerModule.createDocument(app, swaggerConfig, { - extraModels: [PageDto, PageMetaDto, TransactionResponseDto], - }); - SwaggerModule.setup(`api/v${version}/docs`, app, document, { - swaggerOptions: { - persistAuthorization: true, - tagsSorter: 'alpha', - operationsSorter: 'alpha', - docExpansion: 'none', - filter: true, - showRequestDuration: true, - tryItOutEnabled: true, - }, - customSiteTitle: `Nestera API v${version} Docs`, - }); + const document = SwaggerModule.createDocument(app, swaggerConfig); + SwaggerModule.setup(`api/v${version}/docs`, app, document); } const server = await app.listen(port || 3001); diff --git a/backend/src/migrations/1774445030-CreateSavingsGoalMilestones.ts b/backend/src/migrations/1774445030-CreateSavingsGoalMilestones.ts index 476bbdcb5..7283053e7 100644 --- a/backend/src/migrations/1774445030-CreateSavingsGoalMilestones.ts +++ b/backend/src/migrations/1774445030-CreateSavingsGoalMilestones.ts @@ -23,12 +23,7 @@ export class CreateSavingsGoalMilestones1774445030 implements MigrationInterface default: "'AUTOMATIC'", isNullable: false, }, - { - name: 'achieved', - type: 'boolean', - default: false, - isNullable: false, - }, + { name: 'achieved', type: 'boolean', default: false, isNullable: false }, { name: 'achievedAt', type: 'timestamp', isNullable: true }, { name: 'bonusPoints', type: 'int', default: 0, isNullable: false }, { diff --git a/backend/src/modules/admin/admin-audit-logs.service.ts b/backend/src/modules/admin/admin-audit-logs.service.ts index 4714ebda3..6f38be52c 100644 --- a/backend/src/modules/admin/admin-audit-logs.service.ts +++ b/backend/src/modules/admin/admin-audit-logs.service.ts @@ -1,7 +1,12 @@ import { Injectable, NotFoundException, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Cron } from '@nestjs/schedule'; +import { + Repository, + Between, + Like, + MoreThanOrEqual, + LessThanOrEqual, +} from 'typeorm'; import { AuditLog, AuditAction, @@ -272,10 +277,9 @@ export class AdminAuditLogsService { } /** - * Clean up old audit logs based on retention policy. - * Runs daily at 02:00 UTC. + * Clean up old audit logs based on retention policy + * This should be run as a scheduled task */ - @Cron('0 2 * * *') async cleanupOldLogs(): Promise { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - this.RETENTION_DAYS); diff --git a/backend/src/modules/admin/admin-notifications.controller.ts b/backend/src/modules/admin/admin-notifications.controller.ts index feefc4e63..77bd1225d 100644 --- a/backend/src/modules/admin/admin-notifications.controller.ts +++ b/backend/src/modules/admin/admin-notifications.controller.ts @@ -11,7 +11,6 @@ import { import { ApiTags, ApiBearerAuth, - ApiBody, ApiOperation, ApiResponse, ApiQuery, @@ -32,7 +31,7 @@ import { Role } from '../../common/enums/role.enum'; @Controller('admin/notifications') @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.ADMIN) -@ApiBearerAuth('access-token') +@ApiBearerAuth() export class AdminNotificationsController { constructor( private readonly notificationsService: AdminNotificationsService, @@ -42,7 +41,6 @@ export class AdminNotificationsController { @ApiOperation({ summary: 'Send broadcast notification to all or targeted users', }) - @ApiBody({ type: BroadcastNotificationDto }) @ApiResponse({ status: 200, description: 'Broadcast sent', @@ -60,16 +58,20 @@ export class AdminNotificationsController { @Post('targeted') @ApiOperation({ summary: 'Send targeted notification to filtered users' }) - @ApiBody({ type: BroadcastNotificationDto }) - @ApiResponse({ status: 200, description: 'Targeted notification sent' }) + @ApiResponse({ + status: 200, + description: 'Targeted notification sent', + }) async sendTargetedNotification(@Body() dto: BroadcastNotificationDto) { return await this.notificationsService.broadcastNotification(dto); } @Post('schedule') @ApiOperation({ summary: 'Schedule a notification for future delivery' }) - @ApiBody({ type: ScheduleNotificationDto }) - @ApiResponse({ status: 201, description: 'Notification scheduled' }) + @ApiResponse({ + status: 201, + description: 'Notification scheduled', + }) async scheduleNotification(@Body() dto: ScheduleNotificationDto) { return await this.notificationsService.scheduleNotification(dto); } @@ -85,8 +87,10 @@ export class AdminNotificationsController { @Post('preview') @ApiOperation({ summary: 'Preview notification before sending' }) - @ApiBody({ type: PreviewNotificationDto }) - @ApiResponse({ status: 200, description: 'Preview data' }) + @ApiResponse({ + status: 200, + description: 'Preview data', + }) async previewNotification(@Body() dto: PreviewNotificationDto) { return await this.notificationsService.previewNotification(dto); } @@ -109,7 +113,10 @@ export class AdminNotificationsController { @Get(':id/delivery') @ApiOperation({ summary: 'Get delivery statistics for a notification' }) - @ApiResponse({ status: 200, description: 'Delivery statistics' }) + @ApiResponse({ + status: 200, + description: 'Delivery statistics', + }) async getDeliveryStats(@Param('id') notificationId: string) { return await this.notificationsService.getDeliveryStats(notificationId); } diff --git a/backend/src/modules/admin/admin-withdrawal.controller.ts b/backend/src/modules/admin/admin-withdrawal.controller.ts index ee9ec90f1..4fd0b53cb 100644 --- a/backend/src/modules/admin/admin-withdrawal.controller.ts +++ b/backend/src/modules/admin/admin-withdrawal.controller.ts @@ -11,9 +11,7 @@ import { import { ApiTags, ApiBearerAuth, - ApiBody, ApiOperation, - ApiParam, ApiResponse, } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; @@ -28,7 +26,7 @@ import { RejectWithdrawalDto } from './dto/reject-withdrawal.dto'; import { WithdrawalStatsResponseDto } from './dto/withdrawal-stats.dto'; @ApiTags('admin-withdrawals') -@ApiBearerAuth('access-token') +@ApiBearerAuth() @Controller({ path: 'admin/withdrawals', version: '1' }) @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.ADMIN) @@ -60,7 +58,6 @@ export class AdminWithdrawalController { @Get(':id') @ApiOperation({ summary: 'Get withdrawal request detail' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) @ApiResponse({ status: 200, description: 'Withdrawal request detail' }) @ApiResponse({ status: 404, description: 'Withdrawal request not found' }) async getDetail(@Param('id', ParseUUIDPipe) id: string) { @@ -69,7 +66,6 @@ export class AdminWithdrawalController { @Post(':id/approve') @ApiOperation({ summary: 'Approve a pending withdrawal request' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) @ApiResponse({ status: 200, description: 'Withdrawal request approved' }) @ApiResponse({ status: 400, @@ -85,8 +81,6 @@ export class AdminWithdrawalController { @Post(':id/reject') @ApiOperation({ summary: 'Reject a pending withdrawal request' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiBody({ type: RejectWithdrawalDto }) @ApiResponse({ status: 200, description: 'Withdrawal request rejected' }) @ApiResponse({ status: 400, diff --git a/backend/src/modules/admin/admin.controller.ts b/backend/src/modules/admin/admin.controller.ts index 47fb498af..e40a7d48a 100644 --- a/backend/src/modules/admin/admin.controller.ts +++ b/backend/src/modules/admin/admin.controller.ts @@ -7,8 +7,6 @@ import { Query, UseGuards, BadRequestException, - Post, - Req, } from '@nestjs/common'; import { ApiTags, @@ -17,7 +15,6 @@ import { ApiResponse, } from '@nestjs/swagger'; import { UserService } from '../user/user.service'; -import { GovernanceService } from '../governance/governance.service'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../common/guards/roles.guard'; import { Roles } from '../../common/decorators/roles.decorator'; @@ -33,7 +30,6 @@ import { ApproveKycDto, RejectKycDto } from '../user/dto/update-user.dto'; export class AdminController { constructor( private readonly userService: UserService, - private readonly governanceService: GovernanceService, private readonly rateLimitMonitor: RateLimitMonitorService, ) {} @@ -95,28 +91,16 @@ export class AdminController { ); } - @Post('governance/proposals/:id/cancel') - @ApiOperation({ summary: 'Admin emergency cancellation of a proposal' }) - @ApiResponse({ status: 200, description: 'Proposal cancelled successfully' }) - async cancelProposal( - @Param('id') proposalId: string, - @Body() body: { reason: string }, - @Req() req: any, + @Get('rate-limits/violations/:userId') + @ApiOperation({ summary: 'Get rate limit violations for a specific user' }) + @ApiResponse({ status: 200, description: 'User rate limit violations' }) + getUserViolations( + @Param('userId') userId: string, + @Query('limit') limit?: string, ) { - if (!proposalId) { - throw new BadRequestException('Proposal ID is required'); - } - if (!body.reason || body.reason.trim().length === 0) { - throw new BadRequestException('Cancellation reason is required'); - } - const adminId = req.user?.id; - if (!adminId) { - throw new BadRequestException('Admin authentication required'); - } - return this.governanceService.adminCancelProposal( - proposalId, - adminId, - body.reason.trim(), + return this.rateLimitMonitor.getViolationsByUser( + userId, + limit ? parseInt(limit, 10) : 50, ); } } diff --git a/backend/src/modules/admin/admin.module.ts b/backend/src/modules/admin/admin.module.ts index 70896ef2c..35603f0c2 100644 --- a/backend/src/modules/admin/admin.module.ts +++ b/backend/src/modules/admin/admin.module.ts @@ -5,7 +5,6 @@ import { UserModule } from '../user/user.module'; import { SavingsModule } from '../savings/savings.module'; import { MailModule } from '../mail/mail.module'; import { BlockchainModule } from '../blockchain/blockchain.module'; -import { GovernanceModule } from '../governance/governance.module'; import { CircuitBreakerModule } from '../../common/circuit-breaker/circuit-breaker.module'; import { NotificationsModule } from '../notifications/notifications.module'; import { AdminController } from './admin.controller'; @@ -14,19 +13,20 @@ import { AdminWaitlistController } from './admin-waitlist.controller'; import { AdminUsersController } from './admin-users.controller'; import { AdminWithdrawalController } from './admin-withdrawal.controller'; import { AdminWithdrawalService } from './admin-withdrawal.service'; + import { CircuitBreakerController } from './circuit-breaker.controller'; import { AdminDisputesController } from './admin-disputes.controller'; import { AdminAuditLogsController } from './admin-audit-logs.controller'; import { AdminNotificationsController } from './admin-notifications.controller'; + import { AdminTransactionsController } from './admin-transactions.controller'; + import { AdminUsersService } from './admin-users.service'; import { AdminSavingsService } from './admin-savings.service'; import { AdminDisputesService } from './admin-disputes.service'; import { AdminAuditLogsService } from './admin-audit-logs.service'; -import { AdminAuditLogsArchivalService } from './admin-audit-logs-archival.service'; import { AdminNotificationsService } from './admin-notifications.service'; import { AdminTransactionsService } from './admin-transactions.service'; -import { AuditLogService } from '../../common/services/audit-log.service'; import { AdminTransactionNote } from './entities/admin-transaction-note.entity'; import { User } from '../user/entities/user.entity'; import { UserSubscription } from '../savings/entities/user-subscription.entity'; @@ -36,7 +36,6 @@ import { WithdrawalRequest } from '../savings/entities/withdrawal-request.entity import { AuditLog } from '../../common/entities/audit-log.entity'; import { Transaction } from '../transactions/entities/transaction.entity'; import { Dispute, DisputeTimeline } from '../disputes/entities/dispute.entity'; -import { Notification } from '../notifications/entities/notification.entity'; @Module({ imports: [ @@ -51,13 +50,13 @@ import { Notification } from '../notifications/entities/notification.entity'; AdminTransactionNote, Dispute, DisputeTimeline, + AuditLog, Notification, ]), UserModule, SavingsModule, MailModule, BlockchainModule, - GovernanceModule, CircuitBreakerModule, NotificationsModule, EventEmitterModule, @@ -68,28 +67,16 @@ import { Notification } from '../notifications/entities/notification.entity'; AdminWaitlistController, AdminUsersController, AdminWithdrawalController, - CircuitBreakerController, - AdminDisputesController, - AdminAuditLogsController, - AdminNotificationsController, - AdminTransactionsController, ], providers: [ AdminUsersService, AdminSavingsService, AdminDisputesService, AdminAuditLogsService, - AdminAuditLogsArchivalService, AdminNotificationsService, AdminTransactionsService, AdminWithdrawalService, - AuditLogService, - ], - exports: [ - AdminDisputesService, - AdminAuditLogsService, - AdminAuditLogsArchivalService, - AuditLogService, ], + exports: [AdminDisputesService, AdminAuditLogsService], }) export class AdminModule {} diff --git a/backend/src/modules/alerts/alerts.controller.ts b/backend/src/modules/alerts/alerts.controller.ts index 2f61b69c4..fe621fefa 100644 --- a/backend/src/modules/alerts/alerts.controller.ts +++ b/backend/src/modules/alerts/alerts.controller.ts @@ -10,9 +10,7 @@ import { } from '@nestjs/common'; import { ApiBearerAuth, - ApiBody, ApiOperation, - ApiParam, ApiResponse, ApiTags, } from '@nestjs/swagger'; @@ -23,7 +21,7 @@ import { SnoozeAlertDto } from './dto/snooze-alert.dto'; import { AlertsService } from './alerts.service'; @ApiTags('alerts') -@ApiBearerAuth('access-token') +@ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('alerts') export class AlertsController { @@ -31,7 +29,6 @@ export class AlertsController { @Post('create') @ApiOperation({ summary: 'Create product alert or watch' }) - @ApiBody({ type: CreateAlertDto }) @ApiResponse({ status: 201, description: 'Alert created' }) create(@CurrentUser() user: { id: string }, @Body() dto: CreateAlertDto) { return this.alertsService.createAlert(user.id, dto); @@ -39,24 +36,18 @@ export class AlertsController { @Get() @ApiOperation({ summary: 'List active alerts for current user' }) - @ApiResponse({ status: 200, description: 'List of active alerts' }) list(@CurrentUser() user: { id: string }) { return this.alertsService.getUserAlerts(user.id); } @Get('history') @ApiOperation({ summary: 'Get alert history' }) - @ApiResponse({ status: 200, description: 'Alert history' }) history(@CurrentUser() user: { id: string }) { return this.alertsService.getAlertHistory(user.id); } @Patch(':id/snooze') @ApiOperation({ summary: 'Snooze an alert' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiBody({ type: SnoozeAlertDto }) - @ApiResponse({ status: 200, description: 'Alert snoozed' }) - @ApiResponse({ status: 404, description: 'Alert not found' }) snooze( @CurrentUser() user: { id: string }, @Param('id') alertId: string, @@ -67,16 +58,12 @@ export class AlertsController { @Delete(':id') @ApiOperation({ summary: 'Disable an alert' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiResponse({ status: 200, description: 'Alert disabled' }) - @ApiResponse({ status: 404, description: 'Alert not found' }) disable(@CurrentUser() user: { id: string }, @Param('id') alertId: string) { return this.alertsService.disableAlert(user.id, alertId); } @Get('templates/common') @ApiOperation({ summary: 'Common alert templates' }) - @ApiResponse({ status: 200, description: 'List of alert templates' }) templates() { return this.alertsService.alertTemplates(); } diff --git a/backend/src/modules/analytics/analytics.controller.ts b/backend/src/modules/analytics/analytics.controller.ts index 08db7e175..3d2a55f1f 100644 --- a/backend/src/modules/analytics/analytics.controller.ts +++ b/backend/src/modules/analytics/analytics.controller.ts @@ -21,16 +21,15 @@ import { RebalancingQueryDto } from './dto/rebalancing-query.dto'; import { ExecuteRebalancingDto } from './dto/execute-rebalancing.dto'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; -import { AnalyticsDateRangeQueryDto } from './dto/analytics-date-range-query.dto'; @ApiTags('analytics') @Controller('analytics') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() export class AnalyticsController { constructor(private readonly analyticsService: AnalyticsService) {} @Get('portfolio') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Generate portfolio net worth timeline', description: @@ -58,8 +57,6 @@ export class AnalyticsController { } @Get('allocation') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Get asset allocation breakdown for doughnut chart', description: @@ -87,8 +84,6 @@ export class AnalyticsController { } @Get('yield-breakdown') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Get yield breakdown by savings pool', description: @@ -107,8 +102,6 @@ export class AnalyticsController { } @Get('rebalancing-suggestions') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Get risk-adjusted portfolio rebalancing suggestions', }) @@ -127,8 +120,6 @@ export class AnalyticsController { } @Post('rebalancing-suggestions/execute') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() @ApiOperation({ summary: 'Execute one-click portfolio rebalancing', }) @@ -142,56 +133,4 @@ export class AnalyticsController { body.riskProfile || 'balanced', ); } - - @Get('financial-summary') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Get comprehensive financial summary for authenticated user', - }) - async getFinancialSummary(@CurrentUser() user: { id: string }) { - return this.analyticsService.getFinancialSummary(user.id); - } - - @Get('transactions/summary') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Get transaction analytics summary for a date range', - }) - async getTransactionSummary( - @CurrentUser() user: { id: string }, - @Query() query: AnalyticsDateRangeQueryDto, - ) { - return this.analyticsService.getTransactionSummary( - user.id, - query.startDate, - query.endDate, - ); - } - - @Get('savings/performance') - @UseGuards(JwtAuthGuard) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Get savings performance report with benchmarks', - }) - async getSavingsPerformance( - @CurrentUser() user: { id: string }, - @Query() query: AnalyticsDateRangeQueryDto, - ) { - return this.analyticsService.getSavingsPerformance( - user.id, - query.startDate, - query.endDate, - ); - } - - @Get('platform/health') - @ApiOperation({ - summary: 'Get public platform health metrics', - }) - async getPlatformHealthMetrics() { - return this.analyticsService.getPlatformHealthMetrics(); - } } diff --git a/backend/src/modules/analytics/analytics.module.ts b/backend/src/modules/analytics/analytics.module.ts index 9ad03df85..b6ba11f4c 100644 --- a/backend/src/modules/analytics/analytics.module.ts +++ b/backend/src/modules/analytics/analytics.module.ts @@ -9,7 +9,6 @@ import { UserSubscription } from '../savings/entities/user-subscription.entity'; import { ProcessedStellarEvent } from '../blockchain/entities/processed-event.entity'; import { LedgerTransaction } from '../blockchain/entities/transaction.entity'; import { RebalancingExecution } from './entities/rebalancing-execution.entity'; -import { SavingsProduct } from '../savings/entities/savings-product.entity'; @Module({ imports: [ @@ -19,7 +18,6 @@ import { SavingsProduct } from '../savings/entities/savings-product.entity'; LedgerTransaction, UserSubscription, RebalancingExecution, - SavingsProduct, ]), BlockchainModule, // Import to use OracleService for USD conversion NotificationsModule, diff --git a/backend/src/modules/analytics/analytics.service.spec.ts b/backend/src/modules/analytics/analytics.service.spec.ts index b8161a07b..4e3a23ae3 100644 --- a/backend/src/modules/analytics/analytics.service.spec.ts +++ b/backend/src/modules/analytics/analytics.service.spec.ts @@ -8,14 +8,12 @@ import { SavingsService as BlockchainSavingsService } from '../blockchain/saving import { StellarService } from '../blockchain/stellar.service'; import { OracleService } from '../blockchain/oracle.service'; import { PortfolioTimeframe } from './dto/portfolio-timeline-query.dto'; -import { SavingsProduct } from '../savings/entities/savings-product.entity'; describe('AnalyticsService', () => { let service: AnalyticsService; let userRepository: { findOne: jest.Mock }; let eventRepository: { find: jest.Mock }; let transactionRepository: { find: jest.Mock }; - let savingsProductRepository: { createQueryBuilder: jest.Mock }; let blockchainSavingsService: { getUserSavingsBalance: jest.Mock }; let stellarService: { getHorizonServer: jest.Mock }; let oracleService: { @@ -37,9 +35,6 @@ describe('AnalyticsService', () => { transactionRepository = { find: jest.fn(), }; - savingsProductRepository = { - createQueryBuilder: jest.fn(), - }; blockchainSavingsService = { getUserSavingsBalance: jest.fn(), @@ -71,10 +66,6 @@ describe('AnalyticsService', () => { provide: getRepositoryToken(LedgerTransaction), useValue: transactionRepository, }, - { - provide: getRepositoryToken(SavingsProduct), - useValue: savingsProductRepository, - }, { provide: BlockchainSavingsService, useValue: blockchainSavingsService, diff --git a/backend/src/modules/analytics/analytics.service.ts b/backend/src/modules/analytics/analytics.service.ts index c5b943e2d..2d5f4f4fe 100644 --- a/backend/src/modules/analytics/analytics.service.ts +++ b/backend/src/modules/analytics/analytics.service.ts @@ -1,8 +1,6 @@ -import { Inject, Injectable, Logger, Optional } from '@nestjs/common'; -import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { Injectable, Logger, Optional } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Between } from 'typeorm'; -import { Cache } from 'cache-manager'; import { User } from '../user/entities/user.entity'; import { UserSubscription, @@ -25,7 +23,6 @@ import { } from './dto/asset-allocation.dto'; import { YieldBreakdownDto } from './dto/yield-breakdown.dto'; import { RebalancingExecution } from './entities/rebalancing-execution.entity'; -import { SavingsProduct } from '../savings/entities/savings-product.entity'; @Injectable() export class AnalyticsService { @@ -38,8 +35,6 @@ export class AnalyticsService { private readonly eventRepository: Repository, @InjectRepository(LedgerTransaction) private readonly transactionRepository: Repository, - @InjectRepository(SavingsProduct) - private readonly savingsProductRepository: Repository, private readonly blockchainSavingsService: BlockchainSavingsService, private readonly stellarService: StellarService, private readonly oracleService: OracleService, @@ -51,349 +46,8 @@ export class AnalyticsService { private readonly rebalancingRepository?: Repository, @Optional() private readonly notificationsService?: NotificationsService, - @Optional() - @Inject(CACHE_MANAGER) - private readonly cacheManager?: Cache, ) {} - async getFinancialSummary(userId: string) { - const cacheKey = `analytics:financial-summary:${userId}`; - if (this.cacheManager) { - const cached = await this.cacheManager.get(cacheKey); - if (cached) { - return cached; - } - } - - const now = new Date(); - const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); - const previousMonthStart = new Date( - now.getFullYear(), - now.getMonth() - 1, - 1, - ); - const previousYearStart = new Date( - now.getFullYear() - 1, - now.getMonth(), - 1, - ); - - const transactions = await this.transactionRepository.find({ - where: { userId }, - order: { createdAt: 'DESC' }, - }); - - const activeSubscriptions = this.subscriptionRepository - ? await this.subscriptionRepository.find({ - where: { userId, status: SubscriptionStatus.ACTIVE }, - relations: ['product'], - }) - : []; - - const totalDeposits = this.sumByType( - transactions, - LedgerTransactionType.DEPOSIT, - ); - const totalWithdrawals = this.sumByType( - transactions, - LedgerTransactionType.WITHDRAW, - ); - const totalInterestEarned = this.sumByType( - transactions, - LedgerTransactionType.YIELD, - ); - const totalSavings = Number( - activeSubscriptions - .reduce((sum, entry) => sum + Number(entry.amount), 0) - .toFixed(2), - ); - - const principalBalance = Math.max(totalDeposits - totalWithdrawals, 0); - const savingsRate = - totalDeposits > 0 - ? Number(((totalSavings / totalDeposits) * 100).toFixed(2)) - : 0; - const growthPercentage = - principalBalance > 0 - ? Number(((totalInterestEarned / principalBalance) * 100).toFixed(2)) - : 0; - - const allocationMap = new Map(); - for (const sub of activeSubscriptions) { - const key = sub.product?.name || 'Unknown Product'; - allocationMap.set( - key, - (allocationMap.get(key) ?? 0) + Number(sub.amount), - ); - } - - const productAllocation = [...allocationMap.entries()].map( - ([productName, amount]) => ({ - productName, - amount: Number(amount.toFixed(2)), - percentage: - totalSavings > 0 - ? Number(((amount / totalSavings) * 100).toFixed(2)) - : 0, - }), - ); - - const currentMonthTotal = this.sumForPeriod( - transactions, - currentMonthStart, - now, - ); - const previousMonthTotal = this.sumForPeriod( - transactions, - previousMonthStart, - currentMonthStart, - ); - const sameMonthLastYearTotal = this.sumForPeriod( - transactions, - previousYearStart, - new Date(now.getFullYear() - 1, now.getMonth() + 1, 1), - ); - - const monthOverMonthChange = this.percentChange( - previousMonthTotal, - currentMonthTotal, - ); - const yearOverYearChange = this.percentChange( - sameMonthLastYearTotal, - currentMonthTotal, - ); - - const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, 1); - const recentInterest = transactions - .filter( - (tx) => - tx.type === LedgerTransactionType.YIELD && - tx.createdAt >= threeMonthsAgo && - tx.createdAt <= now, - ) - .reduce((sum, tx) => sum + Number(tx.amount), 0); - const avgMonthlyInterest = recentInterest / 3; - const projected12MonthsEarnings = Number( - (avgMonthlyInterest * 12).toFixed(2), - ); - - const summary = { - totalSavings, - totalInterestEarned, - totalDeposits, - totalWithdrawals, - savingsRate, - growthPercentage, - allocation: productAllocation.sort((a, b) => b.amount - a.amount), - comparisons: { - monthOverMonthChange, - yearOverYearChange, - }, - projected12MonthsEarnings, - generatedAt: now.toISOString(), - }; - - if (this.cacheManager) { - await this.cacheManager.set(cacheKey, summary, 60 * 60 * 1000); - } - - return summary; - } - - async getTransactionSummary( - userId: string, - startDate?: string, - endDate?: string, - ) { - const now = new Date(); - const start = startDate - ? new Date(startDate) - : new Date(now.getFullYear(), now.getMonth() - 1, 1); - const end = endDate ? new Date(endDate) : now; - - const txs = await this.transactionRepository.find({ - where: { userId }, - order: { createdAt: 'ASC' }, - }); - - const inRange = txs.filter( - (tx) => tx.createdAt >= start && tx.createdAt <= end, - ); - const byType = { - deposit: this.sumByType(inRange, LedgerTransactionType.DEPOSIT), - withdrawal: this.sumByType(inRange, LedgerTransactionType.WITHDRAW), - interest: this.sumByType(inRange, LedgerTransactionType.YIELD), - }; - - const totalVolume = inRange.reduce((sum, tx) => sum + Number(tx.amount), 0); - const averageTransactionSize = - inRange.length > 0 - ? Number((totalVolume / inRange.length).toFixed(2)) - : 0; - const dayCount = Math.max( - Math.ceil((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000)), - 1, - ); - const averageFrequencyPerDay = Number( - (inRange.length / dayCount).toFixed(2), - ); - - const peakByHour = this.findPeakBucket(inRange, (tx) => - String(tx.createdAt.getHours()), - ); - const peakByDay = this.findPeakBucket(inRange, (tx) => - tx.createdAt.toLocaleDateString('en-US', { weekday: 'long' }), - ); - - const previousStart = new Date(start); - previousStart.setMonth(previousStart.getMonth() - 1); - const previousEnd = new Date(end); - previousEnd.setMonth(previousEnd.getMonth() - 1); - const previousTxs = txs.filter( - (tx) => tx.createdAt >= previousStart && tx.createdAt <= previousEnd, - ); - const previousTotal = previousTxs.reduce( - (sum, tx) => sum + Number(tx.amount), - 0, - ); - - return { - range: { startDate: start.toISOString(), endDate: end.toISOString() }, - breakdownByType: byType, - averageTransactionSize, - averageFrequencyPerDay, - peakTransactionHour: peakByHour, - peakTransactionDay: peakByDay, - monthOverMonthChange: this.percentChange(previousTotal, totalVolume), - chartData: this.buildDailyTransactionChart(inRange), - }; - } - - async getSavingsPerformance( - userId: string, - startDate?: string, - endDate?: string, - ) { - const now = new Date(); - const start = startDate - ? new Date(startDate) - : new Date(now.getFullYear(), now.getMonth() - 6, 1); - const end = endDate ? new Date(endDate) : now; - - const subscriptions = this.subscriptionRepository - ? await this.subscriptionRepository.find({ - where: { userId, status: SubscriptionStatus.ACTIVE }, - relations: ['product'], - }) - : []; - - const platformAverages = await this.savingsProductRepository - .createQueryBuilder('product') - .select('AVG(product.interestRate)', 'avgApy') - .getRawOne<{ avgApy: string | null }>(); - - const benchmarkApy = Number(platformAverages?.avgApy ?? 0); - const products = subscriptions.map((sub) => { - const amount = Number(sub.amount); - const apy = Number(sub.product?.interestRate ?? 0); - const projectedReturn = Number(((amount * apy) / 100).toFixed(2)); - const actualReturn = Number( - Number(sub.totalInterestEarned ?? 0).toFixed(2), - ); - const volatilityPenalty = this.riskPenaltyFor(sub.product?.riskLevel); - const riskAdjustedReturn = Number( - (actualReturn - volatilityPenalty).toFixed(2), - ); - - return { - productId: sub.productId, - productName: sub.product?.name ?? 'Unknown Product', - principal: Number(amount.toFixed(2)), - projectedReturn, - actualReturn, - benchmarkReturn: Number(((amount * benchmarkApy) / 100).toFixed(2)), - riskAdjustedReturn, - }; - }); - - const sorted = [...products].sort( - (a, b) => b.actualReturn - a.actualReturn, - ); - return { - range: { startDate: start.toISOString(), endDate: end.toISOString() }, - benchmarkApy: Number(benchmarkApy.toFixed(2)), - totals: { - projected: Number( - products - .reduce((sum, item) => sum + item.projectedReturn, 0) - .toFixed(2), - ), - actual: Number( - products.reduce((sum, item) => sum + item.actualReturn, 0).toFixed(2), - ), - }, - productPerformance: products, - bestPerformer: sorted[0] ?? null, - worstPerformer: sorted.length ? sorted[sorted.length - 1] : null, - }; - } - - async getPlatformHealthMetrics() { - const cacheKey = 'analytics:platform-health'; - if (this.cacheManager) { - const cached = await this.cacheManager.get(cacheKey); - if (cached) { - return cached; - } - } - - const [tvlRaw, activeUsers, activeSavingsAccounts, avgApyRaw, interestRaw] = - await Promise.all([ - this.savingsProductRepository - .createQueryBuilder('product') - .select('COALESCE(SUM(product.tvlAmount), 0)', 'tvl') - .getRawOne<{ tvl: string }>(), - this.userRepository.count({ where: { isActive: true } }), - this.subscriptionRepository - ? this.subscriptionRepository.count({ - where: { status: SubscriptionStatus.ACTIVE }, - }) - : Promise.resolve(0), - this.savingsProductRepository - .createQueryBuilder('product') - .select('COALESCE(AVG(product.interestRate), 0)', 'avgApy') - .where('product.isActive = :isActive', { isActive: true }) - .getRawOne<{ avgApy: string }>(), - this.subscriptionRepository - ? this.subscriptionRepository - .createQueryBuilder('sub') - .select( - 'COALESCE(SUM(sub.totalInterestEarned), 0)', - 'totalInterest', - ) - .getRawOne<{ totalInterest: string }>() - : Promise.resolve({ totalInterest: '0' }), - ]); - - const payload = { - totalValueLocked: Number(Number(tvlRaw?.tvl ?? 0).toFixed(2)), - activeUsers, - activeSavingsAccounts, - averageAPY: Number(Number(avgApyRaw?.avgApy ?? 0).toFixed(2)), - totalInterestDistributed: Number( - Number(interestRaw?.totalInterest ?? 0).toFixed(2), - ), - platformUptime: 99.98, - lastAuditDate: '2026-01-15', - }; - - if (this.cacheManager) { - await this.cacheManager.set(cacheKey, payload, 15 * 60 * 1000); - } - - return payload; - } - /** * Reconstructs the historical net worth timeline by working backward * from the current live balance using transaction events. @@ -755,68 +409,6 @@ export class AnalyticsService { return poolNames[poolId] || poolId; } - private sumByType( - transactions: LedgerTransaction[], - type: LedgerTransactionType, - ): number { - return Number( - transactions - .filter((tx) => tx.type === type) - .reduce((sum, tx) => sum + Number(tx.amount), 0) - .toFixed(2), - ); - } - - private sumForPeriod( - transactions: LedgerTransaction[], - start: Date, - end: Date, - ): number { - return transactions - .filter((tx) => tx.createdAt >= start && tx.createdAt < end) - .reduce((sum, tx) => sum + Number(tx.amount), 0); - } - - private percentChange(previous: number, current: number): number { - if (previous === 0) { - return current === 0 ? 0 : 100; - } - return Number((((current - previous) / previous) * 100).toFixed(2)); - } - - private findPeakBucket( - transactions: LedgerTransaction[], - keySelector: (transaction: LedgerTransaction) => string, - ) { - const buckets = new Map(); - for (const tx of transactions) { - const key = keySelector(tx); - buckets.set(key, (buckets.get(key) ?? 0) + 1); - } - if (!buckets.size) { - return null; - } - const top = [...buckets.entries()].sort((a, b) => b[1] - a[1])[0]; - return { label: top[0], count: top[1] }; - } - - private buildDailyTransactionChart(transactions: LedgerTransaction[]) { - const grouped = new Map(); - for (const tx of transactions) { - const key = tx.createdAt.toISOString().slice(0, 10); - grouped.set(key, (grouped.get(key) ?? 0) + Number(tx.amount)); - } - return [...grouped.entries()] - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([date, value]) => ({ date, value: Number(value.toFixed(2)) })); - } - - private riskPenaltyFor(riskLevel?: string): number { - if (riskLevel === 'HIGH') return 1.5; - if (riskLevel === 'MEDIUM') return 0.75; - return 0.25; - } - private aggregateCurrentRiskAllocation(subscriptions: UserSubscription[]) { const total = subscriptions.reduce( (sum, subscription) => sum + Number(subscription.amount), diff --git a/backend/src/modules/analytics/analytics.service.yield.spec.ts b/backend/src/modules/analytics/analytics.service.yield.spec.ts index 5392f7138..06d017bd6 100644 --- a/backend/src/modules/analytics/analytics.service.yield.spec.ts +++ b/backend/src/modules/analytics/analytics.service.yield.spec.ts @@ -10,7 +10,6 @@ import { import { SavingsService as BlockchainSavingsService } from '../blockchain/savings.service'; import { StellarService } from '../blockchain/stellar.service'; import { OracleService } from '../blockchain/oracle.service'; -import { SavingsProduct } from '../savings/entities/savings-product.entity'; describe('AnalyticsService - Yield Breakdown', () => { let service: AnalyticsService; @@ -36,10 +35,6 @@ describe('AnalyticsService - Yield Breakdown', () => { provide: getRepositoryToken(LedgerTransaction), useValue: transactionRepository, }, - { - provide: getRepositoryToken(SavingsProduct), - useValue: { createQueryBuilder: jest.fn() }, - }, { provide: BlockchainSavingsService, useValue: { getUserSavingsBalance: jest.fn() }, diff --git a/backend/src/modules/backup/backup-restore-test.service.ts b/backend/src/modules/backup/backup-restore-test.service.ts index 96f18f6e2..cf79cea5f 100644 --- a/backend/src/modules/backup/backup-restore-test.service.ts +++ b/backend/src/modules/backup/backup-restore-test.service.ts @@ -34,8 +34,7 @@ export class BackupRestoreTestService { 'hex', ); this.tmpDir = this.config.get('backup.tmpDir') ?? '/tmp'; - this.testDbName = - this.config.get('backup.testDb.name') || 'nestera_restore_test'; + this.testDbName = 'nestera_restore_test'; this.s3 = new S3Client({ region: this.config.get('backup.s3Region') ?? 'us-east-1', @@ -130,23 +129,28 @@ export class BackupRestoreTestService { } private async decrypt(inputFile: string, outputFile: string): Promise { - const iv = Buffer.alloc(16); - const fd = fs.openSync(inputFile, 'r'); - fs.readSync(fd, iv, 0, 16, 0); - fs.closeSync(fd); - - const input = fs.createReadStream(inputFile, { start: 16 }); - const decipher = crypto.createDecipheriv( - 'aes-256-cbc', - this.encryptionKey, - iv, - ); + const input = fs.createReadStream(inputFile); const output = fs.createWriteStream(outputFile); + // Read the 16-byte IV prepended during encryption await new Promise((resolve, reject) => { - input.pipe(decipher).pipe(output); - output.on('finish', resolve); - output.on('error', reject); + const chunks: Buffer[] = []; + input.on('data', (chunk: Buffer) => chunks.push(chunk)); + input.on('end', () => { + const data = Buffer.concat(chunks); + const iv = data.subarray(0, 16); + const encrypted = data.subarray(16); + const decipher = crypto.createDecipheriv( + 'aes-256-cbc', + this.encryptionKey, + iv, + ); + output.write(decipher.update(encrypted)); + output.write(decipher.final()); + output.end(); + output.on('finish', resolve); + output.on('error', reject); + }); input.on('error', reject); }); } diff --git a/backend/src/modules/backup/backup.controller.ts b/backend/src/modules/backup/backup.controller.ts index 4336bf0c2..9fbaba4e9 100644 --- a/backend/src/modules/backup/backup.controller.ts +++ b/backend/src/modules/backup/backup.controller.ts @@ -5,38 +5,15 @@ import { UseGuards, HttpCode, HttpStatus, - UseInterceptors, - UploadedFile, - ParseFilePipe, - MaxFileSizeValidator, - BadRequestException, } from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; - -// Define File type for multer uploads -interface File { - fieldname: string; - originalname: string; - encoding: string; - mimetype: string; - size: number; - destination?: string; - filename?: string; - path?: string; - buffer?: Buffer; -} import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../common/guards/roles.guard'; import { Roles } from '../../common/decorators/roles.decorator'; import { Role } from '../../common/enums/role.enum'; -import { ThrottlerGuard } from '@nestjs/throttler'; import { BackupService } from './backup.service'; import { BackupRestoreTestService } from './backup-restore-test.service'; -// Maximum backup file size: 1GB -const MAX_BACKUP_SIZE = 1024 * 1024 * 1024; - @ApiTags('backup') @ApiBearerAuth() @UseGuards(JwtAuthGuard, RolesGuard) @@ -58,7 +35,6 @@ export class BackupController { status: record.status, sizeBytes: record.sizeBytes, durationMs: record.durationMs, - checksumSha256: record.checksumSha256, }; } @@ -70,46 +46,6 @@ export class BackupController { return { message: 'Restore test initiated' }; } - @Post('restore') - @UseGuards(ThrottlerGuard) // Rate limit restore uploads - @UseInterceptors(FileInterceptor('backup')) - @HttpCode(HttpStatus.ACCEPTED) - @ApiOperation({ - summary: 'Upload and verify a backup file for restoration (admin)', - }) - async uploadBackupForRestore( - @UploadedFile( - new ParseFilePipe({ - validators: [new MaxFileSizeValidator({ maxSize: MAX_BACKUP_SIZE })], - fileIsRequired: true, - }), - ) - file: File, - ) { - if (!file) { - throw new BadRequestException('No backup file provided'); - } - - if (!this.isValidBackupFile(file)) { - throw new BadRequestException( - 'Invalid backup file format. Expected encrypted backup.', - ); - } - - // Verify the backup integrity - const filePath = file.path || file.filename || ''; - const verified = await this.backupService.verifyBackupFile(filePath); - - return { - message: 'Backup file uploaded and verified', - fileSize: file.size, - verified, - nextSteps: verified - ? 'Backup is ready for restore' - : 'Backup verification failed', - }; - } - @Get('records') @ApiOperation({ summary: 'List recent backup records with metrics (admin)' }) async getRecords() { @@ -128,17 +64,6 @@ export class BackupController { ageHours: +ageHours.toFixed(2), sizeBytes: last.sizeBytes, durationMs: last.durationMs, - verified: last.lastVerifiedAt, }; } - - /** - * Check if the uploaded file is a valid backup - */ - private isValidBackupFile(file: File): boolean { - // Check file extension - const validExtensions = ['.enc', '.dump', '.sql', '.backup']; - const fileName = file.originalname.toLowerCase(); - return validExtensions.some((ext) => fileName.endsWith(ext)); - } } diff --git a/backend/src/modules/backup/backup.service.ts b/backend/src/modules/backup/backup.service.ts index 8e0bc7b40..f90e2c096 100644 --- a/backend/src/modules/backup/backup.service.ts +++ b/backend/src/modules/backup/backup.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; -import { In, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { Cron, CronExpression } from '@nestjs/schedule'; import { exec } from 'child_process'; import { promisify } from 'util'; @@ -13,10 +13,8 @@ import { PutObjectCommand, DeleteObjectCommand, ListObjectsV2Command, - GetObjectCommand, } from '@aws-sdk/client-s3'; import { BackupRecord, BackupStatus } from './entities/backup-record.entity'; -import { EventEmitter2 } from '@nestjs/event-emitter'; const execAsync = promisify(exec); @@ -28,17 +26,11 @@ export class BackupService { private readonly encryptionKey: Buffer; private readonly retentionDays: number; private readonly tmpDir: string; - private readonly testDbHost?: string; - private readonly testDbPort?: number; - private readonly testDbUser?: string; - private readonly testDbPassword?: string; - private readonly testDbName?: string; constructor( private readonly config: ConfigService, @InjectRepository(BackupRecord) private readonly backupRepo: Repository, - private readonly eventEmitter?: EventEmitter2, ) { this.bucket = this.config.get('backup.s3Bucket')!; this.encryptionKey = Buffer.from( @@ -47,11 +39,6 @@ export class BackupService { ); this.retentionDays = this.config.get('backup.retentionDays') ?? 30; this.tmpDir = this.config.get('backup.tmpDir') ?? '/tmp'; - this.testDbHost = this.config.get('backup.testDb.host'); - this.testDbPort = this.config.get('backup.testDb.port'); - this.testDbUser = this.config.get('backup.testDb.user'); - this.testDbPassword = this.config.get('backup.testDb.password'); - this.testDbName = this.config.get('backup.testDb.name'); this.s3 = new S3Client({ region: this.config.get('backup.s3Region') ?? 'us-east-1', @@ -69,40 +56,6 @@ export class BackupService { return this.createBackup(); } - // Daily verification at 04:00 UTC (2 hours after backup) - @Cron('0 4 * * *') - async verifyRecentBackups(): Promise { - try { - this.logger.log('Starting backup verification job...'); - const unverified = await this.backupRepo.find({ - where: { status: BackupStatus.SUCCESS }, - order: { createdAt: 'DESC' }, - take: 3, // Verify last 3 successful backups - }); - - for (const record of unverified) { - // Skip if recently verified - if ( - record.lastVerifiedAt && - Date.now() - record.lastVerifiedAt.getTime() < 24 * 60 * 60 * 1000 - ) { - continue; - } - - await this.verifyBackup(record.id); - } - - this.logger.log('Backup verification job completed'); - } catch (error) { - const msg = (error as Error).message; - this.logger.error(`Backup verification job failed: ${msg}`); - this.eventEmitter?.emit('backup.verification.failed', { - error: msg, - timestamp: new Date(), - }); - } - } - async createBackup(): Promise { const start = Date.now(); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); @@ -124,188 +77,33 @@ export class BackupService { fs.unlinkSync(dumpFile); const sizeBytes = fs.statSync(encFile).size; - const checksum = await this.calculateChecksum(encFile); - await this.uploadToS3(encFile, record.s3Key); fs.unlinkSync(encFile); record.sizeBytes = sizeBytes; - record.checksumSha256 = checksum; record.durationMs = Date.now() - start; record.status = BackupStatus.SUCCESS; this.logger.log( - `Backup complete: ${record.filename} (${(sizeBytes / 1024 / 1024).toFixed(2)} MB, ${record.durationMs}ms, checksum: ${checksum.substring(0, 8)}...)`, + `Backup complete: ${record.filename} (${(sizeBytes / 1024 / 1024).toFixed(2)} MB, ${record.durationMs}ms)`, ); - - this.eventEmitter?.emit('backup.created', { - backupId: record.id, - filename: record.filename, - sizeBytes, - checksum, - timestamp: new Date(), - }); } catch (err) { record.errorMessage = (err as Error).message; record.durationMs = Date.now() - start; - record.status = BackupStatus.FAILED; this.logger.error(`Backup failed: ${record.errorMessage}`); this.cleanupFiles(dumpFile, encFile); - - this.eventEmitter?.emit('backup.failed', { - error: record.errorMessage, - timestamp: new Date(), - }); } return this.backupRepo.save(record); } - /** - * Verify backup integrity by calculating checksum - */ - async verifyBackup(backupId: string): Promise { - const record = await this.backupRepo.findOne({ - where: { id: backupId }, - }); - - if (!record) { - throw new Error(`Backup record not found: ${backupId}`); - } - - const start = Date.now(); - - try { - // Download from S3 - const tempFile = path.join(this.tmpDir, `verify-${Date.now()}.enc`); - - await this.downloadFromS3(record.s3Key, tempFile); - - // Verify checksum - const checksum = await this.calculateChecksum(tempFile); - const verified = checksum === record.checksumSha256; - - // Test restore if checksum matches - let restoreTestPassed = false; - if (verified && this.testDbHost) { - try { - restoreTestPassed = await this.testRestore(tempFile); - } catch (err) { - this.logger.warn( - `Restore test failed for ${record.filename}: ${(err as Error).message}`, - ); - } - } - - fs.unlinkSync(tempFile); - - // Update record - if (verified) { - record.lastVerifiedAt = new Date(); - record.status = restoreTestPassed - ? BackupStatus.RESTORE_TEST_PASSED - : BackupStatus.SUCCESS; - record.restoreTestDurationMs = Date.now() - start; - - this.logger.log( - `Backup verified: ${record.filename} (checksum OK, restore: ${restoreTestPassed ? 'PASS' : 'UNTESTED'})`, - ); - - this.eventEmitter?.emit('backup.verified', { - backupId, - checksum, - restoreTestPassed, - duration: record.restoreTestDurationMs, - timestamp: new Date(), - }); - } else { - record.status = BackupStatus.VERIFICATION_FAILED; - record.errorMessage = `Checksum mismatch: expected ${record.checksumSha256}, got ${checksum}`; - - this.logger.error(`Backup verification failed: ${record.filename}`); - - this.eventEmitter?.emit('backup.verification.failed', { - backupId, - error: record.errorMessage, - timestamp: new Date(), - }); - } - - await this.backupRepo.save(record); - return verified; - } catch (err) { - record.status = BackupStatus.VERIFICATION_FAILED; - record.errorMessage = (err as Error).message; - - this.logger.error( - `Backup verification error for ${record.filename}: ${record.errorMessage}`, - ); - - this.eventEmitter?.emit('backup.verification.failed', { - backupId, - error: record.errorMessage, - timestamp: new Date(), - }); - - await this.backupRepo.save(record); - return false; - } - } - - /** - * Test restore on a separate test database - */ - private async testRestore(encryptedBackupPath: string): Promise { - if (!this.testDbHost || !this.testDbUser || !this.testDbName) { - this.logger.warn('Test database not configured, skipping restore test'); - return false; - } - - const testDumpFile = path.join( - this.tmpDir, - `test-restore-${Date.now()}.dump`, - ); - - try { - // Decrypt backup - await this.decrypt(encryptedBackupPath, testDumpFile); - - // Drop test database and recreate - const dropCommand = `dropdb --if-exists -h ${this.testDbHost} -p ${this.testDbPort} -U ${this.testDbUser} ${this.testDbName}`; - const createCommand = `createdb -h ${this.testDbHost} -p ${this.testDbPort} -U ${this.testDbUser} ${this.testDbName}`; - - try { - await execAsync(dropCommand); - } catch (_) { - // Database might not exist - } - - await execAsync(createCommand); - - // Restore from backup - const testDbUrl = `postgresql://${this.testDbUser}:${this.testDbPassword}@${this.testDbHost}:${this.testDbPort}/${this.testDbName}`; - const restoreCommand = `pg_restore --no-password -d "${testDbUrl}" "${testDumpFile}"`; - - await execAsync(restoreCommand); - - this.logger.debug('Restore test completed successfully'); - return true; - } catch (err) { - throw new Error(`Restore test failed: ${(err as Error).message}`); - } finally { - this.cleanupFiles(testDumpFile); - } - } - // Purge backups older than retention window — runs daily at 03:00 UTC @Cron('0 3 * * *') async purgeExpiredBackups(): Promise { const expired = await this.backupRepo .createQueryBuilder('b') .where('b.expiresAt < :now', { now: new Date() }) - .andWhere('b.status IN (:...statuses)', { - statuses: [BackupStatus.SUCCESS, BackupStatus.RESTORE_TEST_PASSED], - }) + .andWhere('b.status = :status', { status: BackupStatus.SUCCESS }) .getMany(); for (const record of expired) { @@ -332,41 +130,11 @@ export class BackupService { async getLastSuccessful(): Promise { return this.backupRepo.findOne({ - where: { - status: In([BackupStatus.SUCCESS, BackupStatus.RESTORE_TEST_PASSED]), - }, + where: { status: BackupStatus.SUCCESS }, order: { createdAt: 'DESC' }, }); } - /** - * Verify an uploaded backup file - */ - async verifyBackupFile(filePath: string): Promise { - try { - if (!fs.existsSync(filePath)) { - throw new Error('Backup file not found'); - } - - // Check file size - const stats = fs.statSync(filePath); - if (stats.size === 0) { - throw new Error('Backup file is empty'); - } - - // Calculate checksum - const checksum = await this.calculateChecksum(filePath); - this.logger.debug(`Uploaded backup checksum: ${checksum}`); - - return true; - } catch (error) { - this.logger.error( - `Backup file verification failed: ${(error as Error).message}`, - ); - return false; - } - } - // ── Private helpers ────────────────────────────────────────────────────── private async pgDump(outFile: string): Promise { @@ -395,39 +163,6 @@ export class BackupService { }); } - private async decrypt(inputFile: string, outputFile: string): Promise { - const iv = Buffer.alloc(16); - const fd = fs.openSync(inputFile, 'r'); - fs.readSync(fd, iv, 0, 16, 0); - fs.closeSync(fd); - - const input = fs.createReadStream(inputFile, { start: 16 }); - const decipher = crypto.createDecipheriv( - 'aes-256-cbc', - this.encryptionKey, - iv, - ); - const output = fs.createWriteStream(outputFile); - - await new Promise((resolve, reject) => { - input.pipe(decipher).pipe(output); - output.on('finish', resolve); - output.on('error', reject); - input.on('error', reject); - }); - } - - private async calculateChecksum(filePath: string): Promise { - return new Promise((resolve, reject) => { - const hash = crypto.createHash('sha256'); - const stream = fs.createReadStream(filePath); - - stream.on('data', (chunk) => hash.update(chunk)); - stream.on('end', () => resolve(hash.digest('hex'))); - stream.on('error', reject); - }); - } - private async uploadToS3(filePath: string, key: string): Promise { const body = fs.createReadStream(filePath); await this.s3.send( @@ -441,24 +176,6 @@ export class BackupService { ); } - private async downloadFromS3(key: string, filePath: string): Promise { - const response = await this.s3.send( - new GetObjectCommand({ Bucket: this.bucket, Key: key }), - ); - - if (!response.Body) { - throw new Error('Empty response from S3'); - } - - const writeStream = fs.createWriteStream(filePath); - - return new Promise((resolve, reject) => { - (response.Body as any).pipe(writeStream); - writeStream.on('finish', resolve); - writeStream.on('error', reject); - }); - } - private retentionExpiry(): Date { const d = new Date(); d.setDate(d.getDate() + this.retentionDays); diff --git a/backend/src/modules/backup/entities/backup-record.entity.ts b/backend/src/modules/backup/entities/backup-record.entity.ts index c3c8a424d..53046f32a 100644 --- a/backend/src/modules/backup/entities/backup-record.entity.ts +++ b/backend/src/modules/backup/entities/backup-record.entity.ts @@ -10,7 +10,6 @@ export enum BackupStatus { FAILED = 'failed', RESTORE_TEST_PASSED = 'restore_test_passed', RESTORE_TEST_FAILED = 'restore_test_failed', - VERIFICATION_FAILED = 'verification_failed', } @Entity('backup_records') @@ -39,15 +38,6 @@ export class BackupRecord { @Column({ type: 'timestamp', nullable: true }) expiresAt?: Date; - @Column({ nullable: true }) - checksumSha256?: string; - - @Column({ type: 'timestamp', nullable: true }) - lastVerifiedAt?: Date; - - @Column({ nullable: true }) - restoreTestDurationMs?: number; - @CreateDateColumn() createdAt: Date; } diff --git a/backend/src/modules/blockchain/blockchain.controller.ts b/backend/src/modules/blockchain/blockchain.controller.ts index bb5439c02..2ea7d8ef0 100644 --- a/backend/src/modules/blockchain/blockchain.controller.ts +++ b/backend/src/modules/blockchain/blockchain.controller.ts @@ -1,11 +1,5 @@ -import { Controller, Get, Param, Post, Query } from '@nestjs/common'; -import { - ApiOperation, - ApiParam, - ApiResponse, - ApiTags, - ApiQuery, -} from '@nestjs/swagger'; +import { Controller, Get, Param, Post } from '@nestjs/common'; +import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { StellarService } from './stellar.service'; import { BalanceSyncService } from './balance-sync.service'; import { TransactionDto } from './dto/transaction.dto'; @@ -26,62 +20,22 @@ export class BlockchainController { @Get('wallets/:publicKey/transactions') @ApiOperation({ - summary: 'Get paginated recent on-chain transactions for a Stellar wallet', + summary: 'Get recent on-chain transactions for a Stellar wallet', }) @ApiParam({ name: 'publicKey', description: 'The Stellar public key (starting with G) of the wallet', example: 'GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN', }) - @ApiQuery({ - name: 'limit', - description: 'Number of transactions per page (default 10, max 200)', - required: false, - example: 10, - }) - @ApiQuery({ - name: 'cursor', - description: 'Pagination cursor (transaction hash) for fetching next page', - required: false, - }) @ApiResponse({ status: 200, - description: - 'Paginated transactions with cursor for pagination and hasMore flag', - schema: { - type: 'object', - properties: { - records: { - type: 'array', - items: { $ref: '#/components/schemas/TransactionDto' }, - }, - nextCursor: { type: 'string', nullable: true }, - hasMore: { type: 'boolean' }, - }, - }, + description: 'Array of recent transactions mapped to sanitized objects', + type: [TransactionDto], }) - async getWalletTransactions( + getWalletTransactions( @Param('publicKey') publicKey: string, - @Query('limit') limit?: number, - @Query('cursor') cursor?: string, - ): Promise { - const sanitizedLimit = limit ? Math.min(Math.max(limit, 1), 200) : 10; - const res = - limit === undefined && cursor === undefined - ? await this.stellarService.getRecentTransactions(publicKey) - : await this.stellarService.getRecentTransactions( - publicKey, - sanitizedLimit, - cursor, - ); - - if (Array.isArray(res)) return res; - // If paginated result returned and no cursor provided, return records for backward compatibility - if (!cursor && res && typeof res === 'object' && 'records' in res) { - return (res as { records: TransactionDto[] }).records; - } - - return res; + ): Promise { + return this.stellarService.getRecentTransactions(publicKey); } @Get('rpc/status') diff --git a/backend/src/modules/blockchain/stellar.service.ts b/backend/src/modules/blockchain/stellar.service.ts index 7f8db889f..7a317e09e 100644 --- a/backend/src/modules/blockchain/stellar.service.ts +++ b/backend/src/modules/blockchain/stellar.service.ts @@ -197,6 +197,7 @@ export class StellarService implements OnModuleInit { } catch (error) { this.logger.error( `Failed to fetch events from ledger ${startLedger}: ${(error as Error).message}`, + error, ); throw error; } @@ -250,6 +251,7 @@ export class StellarService implements OnModuleInit { } catch (error) { this.logger.error( `Failed to fetch delegation for ${publicKey}: ${(error as Error).message}`, + error, ); return null; } @@ -271,23 +273,10 @@ export class StellarService implements OnModuleInit { * @param limit - Maximum number of transactions to return (default 10) * @returns Array of sanitized TransactionDto objects */ - async getRecentTransactions(publicKey: string): Promise; - async getRecentTransactions( - publicKey: string, - limit?: number, - cursor?: string, - ): Promise< - | { records: TransactionDto[]; nextCursor: string | null; hasMore: boolean } - | TransactionDto[] - >; async getRecentTransactions( publicKey: string, - limit: number = 10, - cursor?: string, - ): Promise< - | TransactionDto[] - | { records: TransactionDto[]; nextCursor: string | null; hasMore: boolean } - > { + limit = 10, + ): Promise { try { return await this.rpcClient.executeWithRetry(async (client) => { const horizonServer = client as Horizon.Server; @@ -350,26 +339,12 @@ export class StellarService implements OnModuleInit { }), ); - // If caller requested pagination via cursor, return pagination object - if (cursor) { - const hasMore = transactions.length >= limit; - const nextCursor = - hasMore && results.length > 0 - ? results[results.length - 1].hash - : null; - - return { - records: results, - nextCursor, - hasMore, - }; - } - return results; }, 'horizon'); } catch (error) { this.logger.error( `Failed to fetch transactions for ${publicKey}: ${(error as Error).message}`, + error, ); return []; } diff --git a/backend/src/modules/challenges/challenges.module.ts b/backend/src/modules/challenges/challenges.module.ts index f531f33cd..db9c030be 100644 --- a/backend/src/modules/challenges/challenges.module.ts +++ b/backend/src/modules/challenges/challenges.module.ts @@ -6,12 +6,8 @@ import { UserSubscription } from '../savings/entities/user-subscription.entity'; import { ChallengeAchievement } from './entities/challenge-achievement.entity'; import { ChallengeParticipant } from './entities/challenge-participant.entity'; import { SavingsChallenge } from './entities/savings-challenge.entity'; -import { Challenge } from './entities/challenge.entity'; -import { UserChallenge } from './entities/user-challenge.entity'; import { ChallengesController } from './challenges.controller'; import { ChallengesService } from './challenges.service'; -import { RewardsChallengesController } from './controllers/rewards-challenges.controller'; -import { RewardsChallengesService } from './services/rewards-challenges.service'; @Module({ imports: [ @@ -19,15 +15,13 @@ import { RewardsChallengesService } from './services/rewards-challenges.service' SavingsChallenge, ChallengeParticipant, ChallengeAchievement, - Challenge, - UserChallenge, UserSubscription, User, ]), NotificationsModule, ], - controllers: [ChallengesController, RewardsChallengesController], - providers: [ChallengesService, RewardsChallengesService], - exports: [ChallengesService, RewardsChallengesService], + controllers: [ChallengesController], + providers: [ChallengesService], + exports: [ChallengesService], }) export class ChallengesModule {} diff --git a/backend/src/modules/disputes/disputes.controller.ts b/backend/src/modules/disputes/disputes.controller.ts index cf40beb43..086ab798f 100644 --- a/backend/src/modules/disputes/disputes.controller.ts +++ b/backend/src/modules/disputes/disputes.controller.ts @@ -8,13 +8,7 @@ import { HttpCode, HttpStatus, } from '@nestjs/common'; -import { - ApiBody, - ApiParam, - ApiTags, - ApiOperation, - ApiResponse, -} from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { DisputesService } from './disputes.service'; import { CreateDisputeDto, @@ -31,7 +25,6 @@ export class DisputesController { @Post() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Open a new dispute' }) - @ApiBody({ type: CreateDisputeDto }) @ApiResponse({ status: 201, description: 'Dispute created', type: Dispute }) @ApiResponse({ status: 400, description: 'Invalid claim ID' }) async createDispute( @@ -53,7 +46,6 @@ export class DisputesController { @Get(':id') @ApiOperation({ summary: 'Get dispute by ID' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) @ApiResponse({ status: 200, description: 'Dispute details', type: Dispute }) @ApiResponse({ status: 404, description: 'Dispute not found' }) async getDispute(@Param('id') id: string): Promise { @@ -62,8 +54,6 @@ export class DisputesController { @Patch(':id') @ApiOperation({ summary: 'Update dispute status' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiBody({ type: UpdateDisputeDto }) @ApiResponse({ status: 200, description: 'Dispute updated', type: Dispute }) @ApiResponse({ status: 404, description: 'Dispute not found' }) async updateDispute( @@ -76,8 +66,6 @@ export class DisputesController { @Post(':id/messages') @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Add message/evidence to dispute' }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiBody({ type: AddDisputeMessageDto }) @ApiResponse({ status: 201, description: 'Message added', diff --git a/backend/src/modules/governance/dto/voting-power-response.dto.ts b/backend/src/modules/governance/dto/voting-power-response.dto.ts index be127a5af..fc53a8afd 100644 --- a/backend/src/modules/governance/dto/voting-power-response.dto.ts +++ b/backend/src/modules/governance/dto/voting-power-response.dto.ts @@ -1,36 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -export class VotingPowerBreakdownDto { - @ApiProperty({ - description: "User's own voting power (not delegated to others)", - example: 10000, - }) - ownPower: number; - - @ApiProperty({ - description: 'Voting power delegated to others', - example: 5000, - }) - delegatedToOthers: number; - - @ApiProperty({ - description: 'Voting power delegated from others to this user', - example: 2500, - }) - delegatedFromOthers: number; -} - export class VotingPowerResponseDto { @ApiProperty({ description: "The user's voting power as a formatted string", example: '12,500 NST', }) votingPower: string; - - @ApiProperty({ - description: 'Breakdown of voting power components', - type: VotingPowerBreakdownDto, - required: false, - }) - breakdown?: VotingPowerBreakdownDto; } diff --git a/backend/src/modules/governance/entities/governance-proposal.entity.ts b/backend/src/modules/governance/entities/governance-proposal.entity.ts index 8939c43c6..661fb0b12 100644 --- a/backend/src/modules/governance/entities/governance-proposal.entity.ts +++ b/backend/src/modules/governance/entities/governance-proposal.entity.ts @@ -123,18 +123,6 @@ export class GovernanceProposal { @Column({ type: 'timestamptz', nullable: true }) executedAt: Date | null; - /** Set when proposal is cancelled by admin */ - @Column({ type: 'timestamptz', nullable: true }) - cancelledAt: Date | null; - - /** Admin who cancelled the proposal */ - @Column({ type: 'uuid', nullable: true }) - cancelledBy: string | null; - - /** Reason for cancellation */ - @Column({ type: 'text', nullable: true }) - cancellationReason: string | null; - @CreateDateColumn() createdAt: Date; diff --git a/backend/src/modules/governance/governance-lifecycle.service.spec.ts b/backend/src/modules/governance/governance-lifecycle.service.spec.ts index 2a6d76089..deb634fd2 100644 --- a/backend/src/modules/governance/governance-lifecycle.service.spec.ts +++ b/backend/src/modules/governance/governance-lifecycle.service.spec.ts @@ -1,16 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { - BadRequestException, - ForbiddenException, - NotFoundException, -} from '@nestjs/common'; +import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { GovernanceService } from './governance.service'; -import { - GovernanceProposal, - ProposalStatus, -} from './entities/governance-proposal.entity'; +import { GovernanceProposal, ProposalStatus } from './entities/governance-proposal.entity'; import { Vote } from './entities/vote.entity'; import { Delegation } from './entities/delegation.entity'; import { UserService } from '../user/user.service'; @@ -18,7 +11,6 @@ import { StellarService } from '../blockchain/stellar.service'; import { SavingsService } from '../blockchain/savings.service'; import { TransactionsService } from '../transactions/transactions.service'; import { LedgerTransaction } from '../blockchain/entities/transaction.entity'; -import { User } from '../user/entities/user.entity'; const mockRepo = () => ({ findOneBy: jest.fn(), @@ -38,12 +30,8 @@ describe('GovernanceService – lifecycle & delegation', () => { let proposalRepo: ReturnType; let delegationRepo: ReturnType; let voteRepo: ReturnType; - let userRepo: ReturnType; let userService: { findById: jest.Mock }; - let stellarService: { - getDelegationForUser: jest.Mock; - getRpcServer: jest.Mock; - }; + let stellarService: { getDelegationForUser: jest.Mock; getRpcServer: jest.Mock }; let eventEmitter: { emit: jest.Mock }; const baseProposal = (overrides = {}): Partial => ({ @@ -68,13 +56,10 @@ describe('GovernanceService – lifecycle & delegation', () => { proposalRepo = mockRepo(); delegationRepo = mockRepo(); voteRepo = mockRepo(); - userRepo = mockRepo(); userService = { findById: jest.fn() }; stellarService = { getDelegationForUser: jest.fn(), - getRpcServer: jest.fn().mockReturnValue({ - getLatestLedger: jest.fn().mockResolvedValue({ sequence: 1000 }), - }), + getRpcServer: jest.fn().mockReturnValue({ getLatestLedger: jest.fn().mockResolvedValue({ sequence: 1000 }) }), }; eventEmitter = { emit: jest.fn() }; @@ -83,28 +68,13 @@ describe('GovernanceService – lifecycle & delegation', () => { GovernanceService, { provide: UserService, useValue: userService }, { provide: StellarService, useValue: stellarService }, - { - provide: SavingsService, - useValue: { - getUserVaultBalance: jest.fn().mockResolvedValue(1_000_000_000), - }, - }, + { provide: SavingsService, useValue: { getUserVaultBalance: jest.fn().mockResolvedValue(1_000_000_000) } }, { provide: TransactionsService, useValue: {} }, { provide: EventEmitter2, useValue: eventEmitter }, - { - provide: getRepositoryToken(GovernanceProposal), - useValue: proposalRepo, - }, + { provide: getRepositoryToken(GovernanceProposal), useValue: proposalRepo }, { provide: getRepositoryToken(Vote), useValue: voteRepo }, { provide: getRepositoryToken(Delegation), useValue: delegationRepo }, - { - provide: getRepositoryToken(LedgerTransaction), - useValue: mockRepo(), - }, - { - provide: getRepositoryToken(User), - useValue: userRepo, - }, + { provide: getRepositoryToken(LedgerTransaction), useValue: mockRepo() }, ], }).compile(); @@ -122,9 +92,7 @@ describe('GovernanceService – lifecycle & delegation', () => { it('throws NotFoundException for unknown proposal', async () => { proposalRepo.findOneBy.mockResolvedValue(null); - await expect(service.getProposalStatus('bad')).rejects.toThrow( - NotFoundException, - ); + await expect(service.getProposalStatus('bad')).rejects.toThrow(NotFoundException); }); }); @@ -132,43 +100,25 @@ describe('GovernanceService – lifecycle & delegation', () => { it('queues a passed proposal and sets timelockEndsAt', async () => { const proposal = baseProposal({ status: ProposalStatus.PASSED }); proposalRepo.findOneBy.mockResolvedValue(proposal); - proposalRepo.save.mockResolvedValue({ - ...proposal, - status: ProposalStatus.QUEUED, - timelockEndsAt: new Date(), - }); + proposalRepo.save.mockResolvedValue({ ...proposal, status: ProposalStatus.QUEUED, timelockEndsAt: new Date() }); const result = await service.queueProposal('prop-1', 'user-1'); expect(result.status).toBe(ProposalStatus.QUEUED); - expect(eventEmitter.emit).toHaveBeenCalledWith( - 'governance.proposal.queued', - expect.any(Object), - ); + expect(eventEmitter.emit).toHaveBeenCalledWith('governance.proposal.queued', expect.any(Object)); }); it('throws if proposal is not in Passed state', async () => { - proposalRepo.findOneBy.mockResolvedValue( - baseProposal({ status: ProposalStatus.ACTIVE }), - ); - await expect(service.queueProposal('prop-1', 'user-1')).rejects.toThrow( - BadRequestException, - ); + proposalRepo.findOneBy.mockResolvedValue(baseProposal({ status: ProposalStatus.ACTIVE })); + await expect(service.queueProposal('prop-1', 'user-1')).rejects.toThrow(BadRequestException); }); }); describe('executeProposal', () => { it('executes a queued proposal after timelock', async () => { const past = new Date(Date.now() - 1000); - const proposal = baseProposal({ - status: ProposalStatus.QUEUED, - timelockEndsAt: past, - }); + const proposal = baseProposal({ status: ProposalStatus.QUEUED, timelockEndsAt: past }); proposalRepo.findOneBy.mockResolvedValue(proposal); - proposalRepo.save.mockResolvedValue({ - ...proposal, - status: ProposalStatus.EXECUTED, - executedAt: new Date(), - }); + proposalRepo.save.mockResolvedValue({ ...proposal, status: ProposalStatus.EXECUTED, executedAt: new Date() }); const result = await service.executeProposal('prop-1', 'user-1'); expect(result.status).toBe(ProposalStatus.EXECUTED); @@ -176,59 +126,34 @@ describe('GovernanceService – lifecycle & delegation', () => { it('throws if timelock has not elapsed', async () => { const future = new Date(Date.now() + 100_000); - proposalRepo.findOneBy.mockResolvedValue( - baseProposal({ status: ProposalStatus.QUEUED, timelockEndsAt: future }), - ); - await expect(service.executeProposal('prop-1', 'user-1')).rejects.toThrow( - BadRequestException, - ); + proposalRepo.findOneBy.mockResolvedValue(baseProposal({ status: ProposalStatus.QUEUED, timelockEndsAt: future })); + await expect(service.executeProposal('prop-1', 'user-1')).rejects.toThrow(BadRequestException); }); it('throws if proposal is not queued', async () => { - proposalRepo.findOneBy.mockResolvedValue( - baseProposal({ status: ProposalStatus.PASSED }), - ); - await expect(service.executeProposal('prop-1', 'user-1')).rejects.toThrow( - BadRequestException, - ); + proposalRepo.findOneBy.mockResolvedValue(baseProposal({ status: ProposalStatus.PASSED })); + await expect(service.executeProposal('prop-1', 'user-1')).rejects.toThrow(BadRequestException); }); }); describe('cancelProposal', () => { it('cancels a proposal by its creator', async () => { - const proposal = baseProposal({ - status: ProposalStatus.ACTIVE, - createdByUserId: 'user-1', - }); + const proposal = baseProposal({ status: ProposalStatus.ACTIVE, createdByUserId: 'user-1' }); proposalRepo.findOneBy.mockResolvedValue(proposal); - proposalRepo.save.mockResolvedValue({ - ...proposal, - status: ProposalStatus.CANCELLED, - }); + proposalRepo.save.mockResolvedValue({ ...proposal, status: ProposalStatus.CANCELLED }); const result = await service.cancelProposal('prop-1', 'user-1'); expect(result.status).toBe(ProposalStatus.CANCELLED); }); it('throws ForbiddenException for non-creator', async () => { - proposalRepo.findOneBy.mockResolvedValue( - baseProposal({ createdByUserId: 'other-user' }), - ); - await expect(service.cancelProposal('prop-1', 'user-1')).rejects.toThrow( - ForbiddenException, - ); + proposalRepo.findOneBy.mockResolvedValue(baseProposal({ createdByUserId: 'other-user' })); + await expect(service.cancelProposal('prop-1', 'user-1')).rejects.toThrow(ForbiddenException); }); it('throws if already executed', async () => { - proposalRepo.findOneBy.mockResolvedValue( - baseProposal({ - status: ProposalStatus.EXECUTED, - createdByUserId: 'user-1', - }), - ); - await expect(service.cancelProposal('prop-1', 'user-1')).rejects.toThrow( - BadRequestException, - ); + proposalRepo.findOneBy.mockResolvedValue(baseProposal({ status: ProposalStatus.EXECUTED, createdByUserId: 'user-1' })); + await expect(service.cancelProposal('prop-1', 'user-1')).rejects.toThrow(BadRequestException); }); }); @@ -236,74 +161,45 @@ describe('GovernanceService – lifecycle & delegation', () => { describe('delegate', () => { it('delegates voting power and returns txHash', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); delegationRepo.findOne.mockResolvedValue(null); // no reverse loop delegationRepo.upsert.mockResolvedValue(undefined); const result = await service.delegate('user-1', 'GXYZ'); expect(result.transactionHash).toBeDefined(); - expect(eventEmitter.emit).toHaveBeenCalledWith( - 'governance.delegation.changed', - expect.any(Object), - ); + expect(eventEmitter.emit).toHaveBeenCalledWith('governance.delegation.changed', expect.any(Object)); }); it('throws if user has no public key', async () => { userService.findById.mockResolvedValue({ id: 'user-1', publicKey: null }); - await expect(service.delegate('user-1', 'GXYZ')).rejects.toThrow( - BadRequestException, - ); + await expect(service.delegate('user-1', 'GXYZ')).rejects.toThrow(BadRequestException); }); it('throws on self-delegation', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); - await expect(service.delegate('user-1', 'GABC')).rejects.toThrow( - BadRequestException, - ); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); + await expect(service.delegate('user-1', 'GABC')).rejects.toThrow(BadRequestException); }); it('throws on delegation loop', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); - delegationRepo.findOne.mockResolvedValue({ - delegatorAddress: 'GXYZ', - delegateAddress: 'GABC', - }); - await expect(service.delegate('user-1', 'GXYZ')).rejects.toThrow( - BadRequestException, - ); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); + delegationRepo.findOne.mockResolvedValue({ delegatorAddress: 'GXYZ', delegateAddress: 'GABC' }); + await expect(service.delegate('user-1', 'GXYZ')).rejects.toThrow(BadRequestException); }); }); describe('revokeDelegate', () => { it('deletes the delegation record', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); delegationRepo.delete.mockResolvedValue(undefined); await service.revokeDelegate('user-1'); - expect(delegationRepo.delete).toHaveBeenCalledWith({ - delegatorAddress: 'GABC', - }); + expect(delegationRepo.delete).toHaveBeenCalledWith({ delegatorAddress: 'GABC' }); }); }); describe('getMyDelegation', () => { it('returns delegate address and delegated power count', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); delegationRepo.findOne.mockResolvedValue({ delegateAddress: 'GXYZ' }); delegationRepo.find.mockResolvedValue([{ delegatorAddress: 'GDEF' }]); @@ -315,10 +211,7 @@ describe('GovernanceService – lifecycle & delegation', () => { describe('getMyDelegators', () => { it('returns list of delegators', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GABC', - }); + userService.findById.mockResolvedValue({ id: 'user-1', publicKey: 'GABC' }); delegationRepo.find.mockResolvedValue([ { delegatorAddress: 'G111' }, { delegatorAddress: 'G222' }, diff --git a/backend/src/modules/governance/governance-proposals.controller.ts b/backend/src/modules/governance/governance-proposals.controller.ts index 5cd6001e9..40affa9b9 100644 --- a/backend/src/modules/governance/governance-proposals.controller.ts +++ b/backend/src/modules/governance/governance-proposals.controller.ts @@ -19,18 +19,14 @@ import { ApiTags, } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; -import { RolesGuard } from '../../common/guards/roles.guard'; -import { Roles } from '../../common/decorators/roles.decorator'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; import { CreateProposalDto } from './dto/create-proposal.dto'; import { EditProposalDto } from './dto/edit-proposal.dto'; import { CastVoteDto } from './dto/cast-vote.dto'; -import { AdminCancelProposalDto } from './dto/admin-cancel-proposal.dto'; import { ProposalListItemDto } from './dto/proposal-list-item.dto'; import { ProposalResponseDto } from './dto/proposal-response.dto'; import { ProposalVotesResponseDto } from './dto/proposal-votes-response.dto'; import { ProposalStatus } from './entities/governance-proposal.entity'; -import { Role } from '../../common/enums/role.enum'; import { GovernanceService } from './governance.service'; @ApiTags('governance') @@ -179,30 +175,12 @@ export class GovernanceProposalsController { @Get(':id/status') @ApiOperation({ summary: 'Get current proposal lifecycle state' }) - @ApiParam({ - name: 'id', - type: 'string', - format: 'uuid', - description: 'Proposal UUID', - }) - @ApiResponse({ - status: 200, - description: 'Proposal status', - schema: { - type: 'object', - properties: { - status: { type: 'string' }, - timelockEndsAt: { type: 'string', nullable: true }, - executedAt: { type: 'string', nullable: true }, - }, - }, - }) + @ApiParam({ name: 'id', type: 'string', format: 'uuid', description: 'Proposal UUID' }) + @ApiResponse({ status: 200, description: 'Proposal status', schema: { type: 'object', properties: { status: { type: 'string' }, timelockEndsAt: { type: 'string', nullable: true }, executedAt: { type: 'string', nullable: true } } } }) @ApiResponse({ status: 404, description: 'Proposal not found' }) - getProposalStatus(@Param('id') id: string): Promise<{ - status: ProposalStatus; - timelockEndsAt: Date | null; - executedAt: Date | null; - }> { + getProposalStatus( + @Param('id') id: string, + ): Promise<{ status: ProposalStatus; timelockEndsAt: Date | null; executedAt: Date | null }> { return this.governanceService.getProposalStatus(id); } @@ -211,11 +189,7 @@ export class GovernanceProposalsController { @ApiBearerAuth() @ApiOperation({ summary: 'Queue a passed proposal (starts timelock)' }) @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiResponse({ - status: 201, - description: 'Proposal queued', - type: ProposalResponseDto, - }) + @ApiResponse({ status: 201, description: 'Proposal queued', type: ProposalResponseDto }) @ApiResponse({ status: 400, description: 'Proposal not in Passed state' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) queueProposal( @@ -230,15 +204,8 @@ export class GovernanceProposalsController { @ApiBearerAuth() @ApiOperation({ summary: 'Execute a queued proposal after timelock' }) @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiResponse({ - status: 201, - description: 'Proposal executed', - type: ProposalResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Timelock not elapsed or wrong state', - }) + @ApiResponse({ status: 201, description: 'Proposal executed', type: ProposalResponseDto }) + @ApiResponse({ status: 400, description: 'Timelock not elapsed or wrong state' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) executeProposal( @Param('id') id: string, @@ -252,11 +219,7 @@ export class GovernanceProposalsController { @ApiBearerAuth() @ApiOperation({ summary: 'Cancel a proposal (creator only)' }) @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiResponse({ - status: 201, - description: 'Proposal cancelled', - type: ProposalResponseDto, - }) + @ApiResponse({ status: 201, description: 'Proposal cancelled', type: ProposalResponseDto }) @ApiResponse({ status: 403, description: 'Not the proposal creator' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) cancelProposal( @@ -265,31 +228,5 @@ export class GovernanceProposalsController { ): Promise { return this.governanceService.cancelProposal(id, user.id); } - - // ── Admin endpoints ─────────────────────────────────────────────────────── - - @Post(':id/admin-cancel') - @UseGuards(JwtAuthGuard, RolesGuard) - @Roles(Role.ADMIN) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Emergency cancellation by admin', - description: - 'Allows an admin to cancel a malicious or erroneous proposal. Requires a valid reason.', - }) - @ApiParam({ name: 'id', type: 'string', format: 'uuid' }) - @ApiResponse({ - status: 201, - description: 'Proposal cancelled by admin', - type: ProposalResponseDto, - }) - @ApiResponse({ status: 403, description: 'Not an admin' }) - @ApiResponse({ status: 401, description: 'Unauthorized' }) - adminCancelProposal( - @Param('id') id: string, - @Body() dto: AdminCancelProposalDto, - @CurrentUser() user: { id: string }, - ): Promise { - return this.governanceService.adminCancelProposal(id, user.id, dto.reason); - } } + diff --git a/backend/src/modules/governance/governance.service.spec.ts b/backend/src/modules/governance/governance.service.spec.ts index c1b63064e..105cbe3cc 100644 --- a/backend/src/modules/governance/governance.service.spec.ts +++ b/backend/src/modules/governance/governance.service.spec.ts @@ -17,7 +17,6 @@ import { Vote, VoteDirection } from './entities/vote.entity'; import { Delegation } from './entities/delegation.entity'; import { TransactionsService } from '../transactions/transactions.service'; import { LedgerTransaction } from '../blockchain/entities/transaction.entity'; -import { User } from '../user/entities/user.entity'; describe('GovernanceService', () => { let service: GovernanceService; @@ -44,9 +43,6 @@ describe('GovernanceService', () => { create: jest.Mock; save: jest.Mock; }; - let userRepo: { - find: jest.Mock; - }; let transactionsService: any; let transactionRepo: { createQueryBuilder: jest.Mock }; let delegationRepo: { @@ -82,9 +78,6 @@ describe('GovernanceService', () => { create: jest.fn(), save: jest.fn(), }; - userRepo = { - find: jest.fn().mockResolvedValue([]), - }; transactionsService = {}; transactionRepo = { createQueryBuilder: jest.fn() }; delegationRepo = { @@ -115,10 +108,6 @@ describe('GovernanceService', () => { provide: getRepositoryToken(Delegation), useValue: delegationRepo, }, - { - provide: getRepositoryToken(User), - useValue: userRepo, - }, ], }).compile(); @@ -187,7 +176,6 @@ describe('GovernanceService', () => { publicKey: 'GUSERPUBLICKEY123', }); savingsService.getUserVaultBalance.mockResolvedValue(2_000_000_000); - userRepo.find.mockResolvedValue([{ id: 'user-1' }]); proposalRepo.findOne.mockResolvedValue({ onChainId: 7 }); proposalRepo.create.mockImplementation((input) => ({ id: 'proposal-1', @@ -242,7 +230,7 @@ describe('GovernanceService', () => { type: ProposalType.RATE_CHANGE, }), ); - expect(result.requiredQuorum).toBe('100.00000000'); + expect(result.requiredQuorum).toBe('5000.00000000'); expect(result.proposalThreshold).toBe('100.00000000'); expect(result.canEdit).toBe(true); }); @@ -447,226 +435,4 @@ describe('GovernanceService', () => { expect(result.transactionHash).toMatch(/^0x/); }); }); - - describe('delegate (loop detection)', () => { - it('rejects delegation to self', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GUSERPUBLICKEY123', - }); - - await expect( - service.delegate('user-1', 'GUSERPUBLICKEY123'), - ).rejects.toThrow('Cannot delegate to yourself'); - }); - - it('rejects direct loop (A→B→A)', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GA', - }); - // Simulate that GA already delegates to GB - delegationRepo.findOne.mockImplementation(({ where }) => { - if (where.delegatorAddress === 'GA') { - return Promise.resolve({ - delegatorAddress: 'GA', - delegateAddress: 'GB', - }); - } - if (where.delegatorAddress === 'GB' && where.delegateAddress === 'GA') { - return Promise.resolve({ - delegatorAddress: 'GB', - delegateAddress: 'GA', - }); - } - return Promise.resolve(null); - }); - - // Try to delegate from GB to GA (would create loop) - userService.findById.mockResolvedValue({ - id: 'user-2', - publicKey: 'GB', - }); - await expect(service.delegate('user-2', 'GA')).rejects.toThrow( - 'Delegation loop detected in chain', - ); - }); - - it('rejects long chain loop (A→B→C→A)', async () => { - // User A delegates to B - userService.findById.mockResolvedValue({ - id: 'user-a', - publicKey: 'GA', - }); - delegationRepo.findOne.mockImplementation(({ where }) => { - if (where.delegatorAddress === 'GA') { - return Promise.resolve({ - delegatorAddress: 'GA', - delegateAddress: 'GB', - }); - } - if (where.delegatorAddress === 'GB') { - return Promise.resolve({ - delegatorAddress: 'GB', - delegateAddress: 'GC', - }); - } - if (where.delegatorAddress === 'GC') { - return Promise.resolve({ - delegatorAddress: 'GC', - delegateAddress: 'GA', - }); - } - return Promise.resolve(null); - }); - - // Try to delegate from GC to GA (would create A→B→C→A loop) - userService.findById.mockResolvedValue({ - id: 'user-c', - publicKey: 'GC', - }); - await expect(service.delegate('user-c', 'GA')).rejects.toThrow( - 'Delegation loop detected in chain', - ); - }); - - it('rejects delegation exceeding max chain depth', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GF', - }); - // Simulate a chain of 5 delegations (A→B→C→D→E→F) - delegationRepo.findOne.mockImplementation(({ where }) => { - const chains: Record = { - GA: 'GB', - GB: 'GC', - GC: 'GD', - GD: 'GE', - GE: 'GF', - }; - const delegate = chains[where.delegatorAddress as string]; - if (delegate) { - return Promise.resolve({ - delegatorAddress: where.delegatorAddress, - delegateAddress: delegate, - }); - } - return Promise.resolve(null); - }); - delegationRepo.find.mockImplementation(({ where }) => { - const incoming: Record = { - GB: ['GA'], - GC: ['GB'], - GD: ['GC'], - GE: ['GD'], - GF: ['GE'], - }; - return Promise.resolve( - (incoming[where.delegateAddress as string] ?? []).map( - (delegatorAddress) => ({ - delegatorAddress, - delegateAddress: where.delegateAddress, - }), - ), - ); - }); - - // Try to delegate from GF to GG (would be 6th level) - await expect(service.delegate('user-f', 'GG')).rejects.toThrow( - 'Delegation chain would exceed maximum depth of 5', - ); - }); - - it('allows valid delegation with no loop and within depth limit', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GA', - }); - delegationRepo.findOne.mockResolvedValue(null); - delegationRepo.upsert.mockResolvedValue(undefined); - eventEmitter.emit.mockClear(); - - const result = await service.delegate('user-1', 'GB'); - - expect(result.transactionHash).toBeDefined(); - expect(delegationRepo.upsert).toHaveBeenCalledWith( - { delegatorAddress: 'GA', delegateAddress: 'GB' }, - ['delegatorAddress'], - ); - expect(eventEmitter.emit).toHaveBeenCalledWith( - 'governance.delegation.changed', - { delegator: 'GA', delegate: 'GB' }, - ); - }); - }); - - describe('getDelegationChain', () => { - it('returns empty chain for user with no delegation', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GA', - }); - delegationRepo.findOne.mockResolvedValue(null); - - const result = await service.getDelegationChain('user-1'); - - expect(result).toEqual({ chain: [], depth: 0, hasLoop: false }); - }); - - it('returns delegation chain with depth and loop detection', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GA', - }); - delegationRepo.findOne.mockImplementation(({ where }) => { - if (where.delegatorAddress === 'GA') { - return Promise.resolve({ - delegatorAddress: 'GA', - delegateAddress: 'GB', - }); - } - if (where.delegatorAddress === 'GB') { - return Promise.resolve({ - delegatorAddress: 'GB', - delegateAddress: 'GC', - }); - } - return Promise.resolve(null); - }); - - const result = await service.getDelegationChain('user-1'); - - expect(result.chain).toEqual(['GB', 'GC']); - expect(result.depth).toBe(2); - expect(result.hasLoop).toBe(false); - }); - - it('detects loop in delegation chain', async () => { - userService.findById.mockResolvedValue({ - id: 'user-1', - publicKey: 'GA', - }); - delegationRepo.findOne.mockImplementation(({ where }) => { - if (where.delegatorAddress === 'GA') { - return Promise.resolve({ - delegatorAddress: 'GA', - delegateAddress: 'GB', - }); - } - if (where.delegatorAddress === 'GB') { - return Promise.resolve({ - delegatorAddress: 'GB', - delegateAddress: 'GA', - }); - } - return Promise.resolve(null); - }); - - const result = await service.getDelegationChain('user-1'); - - expect(result.chain).toEqual(['GB', 'GA']); - expect(result.depth).toBe(2); - expect(result.hasLoop).toBe(true); - }); - }); }); diff --git a/backend/src/modules/governance/governance.service.ts b/backend/src/modules/governance/governance.service.ts index f25e19c91..772d61589 100644 --- a/backend/src/modules/governance/governance.service.ts +++ b/backend/src/modules/governance/governance.service.ts @@ -7,7 +7,7 @@ import { } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { InjectRepository } from '@nestjs/typeorm'; -import { IsNull, Not, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { StellarService } from '../blockchain/stellar.service'; import { SavingsService } from '../blockchain/savings.service'; import { TransactionsService } from '../transactions/transactions.service'; @@ -27,16 +27,13 @@ import { } from './entities/governance-proposal.entity'; import { Vote, VoteDirection } from './entities/vote.entity'; import { Delegation } from './entities/delegation.entity'; -import { User } from '../user/entities/user.entity'; -import { LedgerTransaction } from '../blockchain/entities/transaction.entity'; import { VotingPowerResponseDto } from './dto/voting-power-response.dto'; +import { TxStatus, TxType } from '../transactions/entities/transaction.entity'; +import { LedgerTransaction } from '../blockchain/entities/transaction.entity'; /** Timelock duration in milliseconds (24 hours) */ const TIMELOCK_DURATION_MS = 24 * 60 * 60 * 1000; -/** Maximum delegation chain depth to prevent infinite loops */ -const MAX_DELEGATION_CHAIN_DEPTH = 5; - @Injectable() export class GovernanceService { constructor( @@ -53,8 +50,6 @@ export class GovernanceService { private readonly transactionRepo: Repository, @InjectRepository(Delegation) private readonly delegationRepo: Repository, - @InjectRepository(User) - private readonly userRepo: Repository, ) {} async createProposal( @@ -94,7 +89,7 @@ export class GovernanceService { dto.title, onChainId, ); - const requiredQuorum = await this.calculateRequiredQuorum(); + const requiredQuorum = this.calculateRequiredQuorum(); const proposal = this.proposalRepo.create({ onChainId, @@ -194,7 +189,7 @@ export class GovernanceService { proposal.attachments = dto.attachments ?? proposal.attachments ?? []; proposal.startBlock = nextStartBlock; proposal.endBlock = nextEndBlock; - proposal.requiredQuorum = (await this.calculateRequiredQuorum()).toFixed(8); + proposal.requiredQuorum = this.calculateRequiredQuorum().toFixed(8); proposal.quorumBps = this.getQuorumBps(); proposal.proposalThreshold = this.getProposalThreshold().toFixed(8); @@ -292,33 +287,6 @@ export class GovernanceService { async getUserVotingPower(userId: string): Promise { const votingPower = await this.getVotingPowerAmount(userId); - const user = await this.userService.findById(userId); - - // Get additional info about delegated power - let delegatedToOthers = 0; - let delegatedFromOthers = 0; - - if (user.publicKey) { - // Check if user has delegated their power to someone else - const myDelegation = await this.delegationRepo.findOne({ - where: { delegatorAddress: user.publicKey }, - }); - if (myDelegation) { - delegatedToOthers = votingPower; // All their power is delegated away - } - - // Get power delegated from others to this user - const delegators = await this.delegationRepo.find({ - where: { delegateAddress: user.publicKey }, - }); - for (const delegation of delegators) { - const delegatorPower = await this.getAddressVotingPower( - delegation.delegatorAddress, - ); - delegatedFromOthers += delegatorPower; - } - } - if (votingPower === 0) { return { votingPower: '0 NST' }; } @@ -326,14 +294,7 @@ export class GovernanceService { minimumFractionDigits: 0, maximumFractionDigits: 0, }); - return { - votingPower: `${formattedVotingPower} NST`, - breakdown: { - ownPower: votingPower - delegatedToOthers, - delegatedToOthers, - delegatedFromOthers, - }, - }; + return { votingPower: `${formattedVotingPower} NST` }; } async castVote( @@ -418,48 +379,29 @@ export class GovernanceService { // ── Lifecycle (#541) ─────────────────────────────────────────────────────── - async getProposalStatus(proposalId: string): Promise<{ - status: ProposalStatus; - timelockEndsAt: Date | null; - executedAt: Date | null; - }> { + async getProposalStatus(proposalId: string): Promise<{ status: ProposalStatus; timelockEndsAt: Date | null; executedAt: Date | null }> { const proposal = await this.proposalRepo.findOneBy({ id: proposalId }); - if (!proposal) - throw new NotFoundException(`Proposal ${proposalId} not found`); - return { - status: proposal.status, - timelockEndsAt: proposal.timelockEndsAt ?? null, - executedAt: proposal.executedAt ?? null, - }; + if (!proposal) throw new NotFoundException(`Proposal ${proposalId} not found`); + return { status: proposal.status, timelockEndsAt: proposal.timelockEndsAt ?? null, executedAt: proposal.executedAt ?? null }; } - async queueProposal( - proposalId: string, - userId: string, - ): Promise { + async queueProposal(proposalId: string, userId: string): Promise { const proposal = await this.proposalRepo.findOneBy({ id: proposalId }); - if (!proposal) - throw new NotFoundException(`Proposal ${proposalId} not found`); + if (!proposal) throw new NotFoundException(`Proposal ${proposalId} not found`); if (proposal.status !== ProposalStatus.PASSED) { throw new BadRequestException('Only passed proposals can be queued'); } proposal.status = ProposalStatus.QUEUED; proposal.timelockEndsAt = new Date(Date.now() + TIMELOCK_DURATION_MS); const saved = await this.proposalRepo.save(proposal); - this.eventEmitter.emit('governance.proposal.queued', { - proposalId: saved.id, - }); + this.eventEmitter.emit('governance.proposal.queued', { proposalId: saved.id }); const currentLedger = await this.getCurrentLedger(); return this.toProposalResponse(saved, currentLedger); } - async executeProposal( - proposalId: string, - userId: string, - ): Promise { + async executeProposal(proposalId: string, userId: string): Promise { const proposal = await this.proposalRepo.findOneBy({ id: proposalId }); - if (!proposal) - throw new NotFoundException(`Proposal ${proposalId} not found`); + if (!proposal) throw new NotFoundException(`Proposal ${proposalId} not found`); if (proposal.status !== ProposalStatus.QUEUED) { throw new BadRequestException('Only queued proposals can be executed'); } @@ -469,280 +411,71 @@ export class GovernanceService { proposal.status = ProposalStatus.EXECUTED; proposal.executedAt = new Date(); const saved = await this.proposalRepo.save(proposal); - this.eventEmitter.emit('governance.proposal.executed', { - proposalId: saved.id, - }); + this.eventEmitter.emit('governance.proposal.executed', { proposalId: saved.id }); const currentLedger = await this.getCurrentLedger(); return this.toProposalResponse(saved, currentLedger); } - async cancelProposal( - proposalId: string, - userId: string, - ): Promise { + async cancelProposal(proposalId: string, userId: string): Promise { const proposal = await this.proposalRepo.findOneBy({ id: proposalId }); - if (!proposal) - throw new NotFoundException(`Proposal ${proposalId} not found`); + if (!proposal) throw new NotFoundException(`Proposal ${proposalId} not found`); if (proposal.createdByUserId !== userId) { throw new ForbiddenException('Only the proposal creator can cancel it'); } - if ( - proposal.status === ProposalStatus.EXECUTED || - proposal.status === ProposalStatus.CANCELLED - ) { - throw new BadRequestException( - `Cannot cancel a proposal with status ${proposal.status}`, - ); - } - proposal.status = ProposalStatus.CANCELLED; - const saved = await this.proposalRepo.save(proposal); - this.eventEmitter.emit('governance.proposal.cancelled', { - proposalId: saved.id, - }); - const currentLedger = await this.getCurrentLedger(); - return this.toProposalResponse(saved, currentLedger); - } - - /** - * Admin emergency cancellation of a proposal. - * Requires admin role and a valid reason. - */ - async adminCancelProposal( - proposalId: string, - adminId: string, - reason: string, - ): Promise { - const proposal = await this.proposalRepo.findOneBy({ id: proposalId }); - if (!proposal) - throw new NotFoundException(`Proposal ${proposalId} not found`); - - if ( - proposal.status === ProposalStatus.EXECUTED || - proposal.status === ProposalStatus.CANCELLED - ) { - throw new BadRequestException( - `Cannot cancel a proposal with status ${proposal.status}`, - ); + if (proposal.status === ProposalStatus.EXECUTED || proposal.status === ProposalStatus.CANCELLED) { + throw new BadRequestException(`Cannot cancel a proposal with status ${proposal.status}`); } - - const previousStatus = proposal.status; - - // Get admin user for audit logging - const admin = await this.userService.findById(adminId); - - // Get proposal creator for notification (if available) - const creator = proposal.createdByUserId - ? await this.userService.findById(proposal.createdByUserId) - : null; - proposal.status = ProposalStatus.CANCELLED; const saved = await this.proposalRepo.save(proposal); - - // Emit emergency cancellation event - this.eventEmitter.emit('governance.proposal.emergency_cancelled', { - proposalId: saved.id, - onChainId: saved.onChainId, - adminId, - adminPublicKey: admin?.publicKey ?? null, - reason, - cancelledAt: new Date().toISOString(), - }); - - // Notify proposal creator - if (creator?.publicKey) { - this.eventEmitter.emit('governance.proposal.cancelled_by_admin', { - proposalId: saved.id, - onChainId: saved.onChainId, - creatorPublicKey: creator.publicKey, - adminPublicKey: admin?.publicKey ?? null, - reason, - cancelledAt: new Date().toISOString(), - }); - } - - // Log in audit trail - this.eventEmitter.emit('audit.log', { - action: 'PROPOSAL_EMERGENCY_CANCELLED', - entityType: 'GovernanceProposal', - entityId: saved.id, - userId: adminId, - details: { - onChainId: saved.onChainId, - title: saved.title, - reason, - previousStatus, - newStatus: ProposalStatus.CANCELLED, - }, - timestamp: new Date().toISOString(), - }); - + this.eventEmitter.emit('governance.proposal.cancelled', { proposalId: saved.id }); const currentLedger = await this.getCurrentLedger(); return this.toProposalResponse(saved, currentLedger); } // ── Delegation (#542) ────────────────────────────────────────────────────── - async delegate( - userId: string, - delegateAddress: string, - ): Promise<{ transactionHash: string }> { + async delegate(userId: string, delegateAddress: string): Promise<{ transactionHash: string }> { const user = await this.userService.findById(userId); - if (!user.publicKey) - throw new BadRequestException('User must have a public key to delegate'); + if (!user.publicKey) throw new BadRequestException('User must have a public key to delegate'); if (user.publicKey === delegateAddress) { throw new BadRequestException('Cannot delegate to yourself'); } - - // Validate the target chain so we reject both new loops and existing - // circular dependencies further down the line. - const targetChain = await this.buildDelegationChain(delegateAddress); - if (targetChain.hasLoop || targetChain.chain.includes(user.publicKey)) { - throw new BadRequestException('Delegation loop detected in chain'); - } - - const incomingDepth = await this.getIncomingDelegationDepth(user.publicKey); - const resultingDepth = incomingDepth + 1 + targetChain.chain.length; - if (resultingDepth > MAX_DELEGATION_CHAIN_DEPTH) { - throw new BadRequestException( - `Delegation chain would exceed maximum depth of ${MAX_DELEGATION_CHAIN_DEPTH}`, - ); - } + // Loop prevention: check if delegateAddress already delegates to user + const reverseLoop = await this.delegationRepo.findOne({ + where: { delegatorAddress: delegateAddress, delegateAddress: user.publicKey }, + }); + if (reverseLoop) throw new BadRequestException('Delegation loop detected'); await this.delegationRepo.upsert( { delegatorAddress: user.publicKey, delegateAddress }, ['delegatorAddress'], ); const txHash = `0x${Math.random().toString(16).slice(2, 10)}${Date.now().toString(16)}`; - this.eventEmitter.emit('governance.delegation.changed', { - delegator: user.publicKey, - delegate: delegateAddress, - }); + this.eventEmitter.emit('governance.delegation.changed', { delegator: user.publicKey, delegate: delegateAddress }); return { transactionHash: txHash }; } - /** - * Builds the full delegation chain from a starting address to detect loops. - * Returns an array of addresses in the chain order. - */ - private async buildDelegationChain( - startAddress: string, - ): Promise<{ chain: string[]; hasLoop: boolean }> { - const chain: string[] = []; - let currentAddress: string | null = startAddress; - const visited = new Set([startAddress]); - - while (currentAddress) { - const delegation = await this.delegationRepo.findOne({ - where: { delegatorAddress: currentAddress }, - }); - - if (delegation) { - const nextAddress = delegation.delegateAddress; - chain.push(nextAddress); - - if (visited.has(nextAddress)) { - return { chain, hasLoop: true }; - } - - visited.add(nextAddress); - currentAddress = nextAddress; - } else { - currentAddress = null; - } - } - - return { chain, hasLoop: false }; - } - - private async getIncomingDelegationDepth( - userAddress: string, - visited = new Set(), - ): Promise { - if (visited.has(userAddress)) { - return 0; - } - - visited.add(userAddress); - const directDelegators = - (await this.delegationRepo.find({ - where: { delegateAddress: userAddress }, - })) ?? []; - - if (!directDelegators.length) { - return 0; - } - - const depths = await Promise.all( - directDelegators.map(async (delegation) => { - const parentDepth = await this.getIncomingDelegationDepth( - delegation.delegatorAddress, - new Set(visited), - ); - return parentDepth + 1; - }), - ); - - return Math.max(...depths); - } - - /** - * Gets the full delegation chain visualization for a user. - * Returns the chain as an array of addresses with depth information. - */ - async getDelegationChain( - userId: string, - ): Promise<{ chain: string[]; depth: number; hasLoop: boolean }> { - const user = await this.userService.findById(userId); - if (!user.publicKey) { - return { chain: [], depth: 0, hasLoop: false }; - } - - const { chain, hasLoop } = await this.buildDelegationChain(user.publicKey); - - return { - chain, - depth: chain.length, - hasLoop, - }; - } - async revokeDelegate(userId: string): Promise { const user = await this.userService.findById(userId); - if (!user.publicKey) - throw new BadRequestException('User must have a public key'); + if (!user.publicKey) throw new BadRequestException('User must have a public key'); await this.delegationRepo.delete({ delegatorAddress: user.publicKey }); - this.eventEmitter.emit('governance.delegation.revoked', { - delegator: user.publicKey, - }); + this.eventEmitter.emit('governance.delegation.revoked', { delegator: user.publicKey }); } - async getMyDelegation( - userId: string, - ): Promise<{ delegate: string | null; totalDelegatedPower: number }> { + async getMyDelegation(userId: string): Promise<{ delegate: string | null; totalDelegatedPower: number }> { const user = await this.userService.findById(userId); if (!user.publicKey) return { delegate: null, totalDelegatedPower: 0 }; - const record = await this.delegationRepo.findOne({ - where: { delegatorAddress: user.publicKey }, - }); - const delegators = await this.delegationRepo.find({ - where: { delegateAddress: user.publicKey }, - }); + const record = await this.delegationRepo.findOne({ where: { delegatorAddress: user.publicKey } }); + const delegators = await this.delegationRepo.find({ where: { delegateAddress: user.publicKey } }); const totalDelegatedPower = delegators.length; // simplified; real impl sums NST balances return { delegate: record?.delegateAddress ?? null, totalDelegatedPower }; } - async getMyDelegators( - userId: string, - ): Promise<{ delegators: string[]; totalDelegatedPower: number }> { + async getMyDelegators(userId: string): Promise<{ delegators: string[]; totalDelegatedPower: number }> { const user = await this.userService.findById(userId); if (!user.publicKey) return { delegators: [], totalDelegatedPower: 0 }; - const records = await this.delegationRepo.find({ - where: { delegateAddress: user.publicKey }, - }); - return { - delegators: records.map((r) => r.delegatorAddress), - totalDelegatedPower: records.length, - }; + const records = await this.delegationRepo.find({ where: { delegateAddress: user.publicKey } }); + return { delegators: records.map((r) => r.delegatorAddress), totalDelegatedPower: records.length }; } async getProposalVotesByOnChainId( @@ -818,48 +551,6 @@ export class GovernanceService { user.publicKey, ); - const ownPower = Number(balance) / 10_000_000; - - // Add delegated voting power from users who delegated to this user - const delegatedPower = await this.getDelegatedVotingPower(user.publicKey); - - return ownPower + delegatedPower; - } - - /** - * Calculates the total voting power delegated to a user. - * This includes the voting power of all users who have delegated to this user. - */ - private async getDelegatedVotingPower(userAddress: string): Promise { - const delegators = await this.delegationRepo.find({ - where: { delegateAddress: userAddress }, - }); - - let totalDelegatedPower = 0; - for (const delegation of delegators) { - const delegatorPower = await this.getAddressVotingPower( - delegation.delegatorAddress, - ); - totalDelegatedPower += delegatorPower; - } - - return totalDelegatedPower; - } - - /** - * Gets the voting power for a specific wallet address. - */ - private async getAddressVotingPower(address: string): Promise { - const governanceTokenContractId = process.env.NST_GOVERNANCE_CONTRACT_ID; - if (!governanceTokenContractId) { - throw new Error('NST governance token contract ID not configured'); - } - - const balance = await this.savingsService.getUserVaultBalance( - governanceTokenContractId, - address, - ); - return Number(balance) / 10_000_000; } @@ -986,33 +677,8 @@ export class GovernanceService { } } - private async calculateRequiredQuorum(): Promise { - // Calculate total voting power including delegated votes - const totalVotingPower = await this.getTotalVotingPower(); - return (totalVotingPower * this.getQuorumBps()) / 10_000; - } - - /** - * Calculates the total voting power in the system including all delegated votes. - */ - private async getTotalVotingPower(): Promise { - // Get all users with public keys - const users = await this.userRepo.find({ - where: { publicKey: Not(IsNull()) }, - select: ['id'], - }); - - if (!users || users.length === 0) { - return 0; - } - - let totalVotingPower = 0; - for (const user of users) { - const userVotingPower = await this.getVotingPowerAmount(user.id); - totalVotingPower += userVotingPower; - } - - return totalVotingPower; + private calculateRequiredQuorum(): number { + return (this.getMaxVotingPower() * this.getQuorumBps()) / 10_000; } private resolveProposalTitle( diff --git a/backend/src/modules/governance/migrations/governance-schema.sql b/backend/src/modules/governance/migrations/governance-schema.sql index 91c3d7129..0929ddbe1 100644 --- a/backend/src/modules/governance/migrations/governance-schema.sql +++ b/backend/src/modules/governance/migrations/governance-schema.sql @@ -11,11 +11,6 @@ CREATE TABLE IF NOT EXISTS governance_proposals ( proposer VARCHAR(255), start_block BIGINT, end_block BIGINT, - timelock_ends_at TIMESTAMP WITH TIME ZONE, - executed_at TIMESTAMP WITH TIME ZONE, - cancelled_at TIMESTAMP WITH TIME ZONE, - cancelled_by UUID, - cancellation_reason TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); diff --git a/backend/src/modules/health/health.controller.ts b/backend/src/modules/health/health.controller.ts index 0f9e86f30..35e3f5646 100644 --- a/backend/src/modules/health/health.controller.ts +++ b/backend/src/modules/health/health.controller.ts @@ -1,11 +1,10 @@ -import { Controller, Get, HttpCode, HttpStatus, Query, Header, Logger } from '@nestjs/common'; +import { Controller, Get, HttpCode, HttpStatus, Query } from '@nestjs/common'; import { HealthCheck, HealthCheckService } from '@nestjs/terminus'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { TypeOrmHealthIndicator } from './indicators/typeorm.health'; import { IndexerHealthIndicator } from './indicators/indexer.health'; import { RpcHealthIndicator } from './indicators/rpc.health'; import { ConnectionPoolHealthIndicator } from './indicators/connection-pool.health'; -import { SystemHealthIndicator } from './indicators/system.health'; import { RedisHealthIndicator, EmailServiceHealthIndicator, @@ -17,8 +16,6 @@ import { HealthHistoryService } from './health-history.service'; @ApiTags('Health') @Controller('health') export class HealthController { - private readonly logger = new Logger(HealthController.name); - constructor( private readonly health: HealthCheckService, private readonly db: TypeOrmHealthIndicator, @@ -29,7 +26,6 @@ export class HealthController { private readonly email: EmailServiceHealthIndicator, private readonly sorobanRpc: SorobanRpcHealthIndicator, private readonly horizon: HorizonHealthIndicator, - private readonly system: SystemHealthIndicator, private readonly healthHistory: HealthHistoryService, ) {} @@ -41,6 +37,57 @@ export class HealthController { description: 'Comprehensive health check including database, RPC endpoints, indexer service, and connection pool', }) + @ApiResponse({ + status: 200, + description: 'Application is healthy', + schema: { + example: { + status: 'ok', + checks: { + database: { + status: 'up', + responseTime: '45ms', + threshold: '200ms', + }, + database_pool: { + status: 'up', + metrics: { + activeConnections: 5, + idleConnections: 15, + utilizationPercentage: 25, + }, + }, + rpc: { + status: 'up', + responseTime: '120ms', + currentEndpoint: 'https://soroban-testnet.stellar.org', + totalEndpoints: 2, + }, + indexer: { + status: 'up', + timeSinceLastProcess: '3500ms', + threshold: '15000ms', + lastProcessedTime: '2026-03-25T10:30:45.123Z', + }, + }, + }, + }, + }) + @ApiResponse({ + status: 503, + description: 'One or more health checks failed', + schema: { + example: { + status: 'error', + checks: { + database: { + status: 'down', + message: 'Database connection failed', + }, + }, + }, + }, + }) async check() { return this.health.check([ () => this.db.isHealthy('database'), @@ -66,51 +113,33 @@ export class HealthController { this.email.isHealthy('email'), this.sorobanRpc.isHealthy('soroban-rpc'), this.horizon.isHealthy('horizon'), - this.system.isHealthy('system'), ]); - const services = [ - 'database', - 'rpc', - 'indexer', - 'redis', - 'email', - 'soroban-rpc', - 'horizon', - 'system', - ]; - const results = checks.map((check, index) => { - const serviceName = services[index]; - let status: 'up' | 'down' | 'degraded' = 'down'; - let responseTime = 0; - let details: any = {}; + const services = [ + 'database', + 'rpc', + 'indexer', + 'redis', + 'email', + 'soroban-rpc', + 'horizon', + ]; if (check.status === 'fulfilled') { - const val = check.value; - const res = val[serviceName]; - status = res?.status === 'up' || res?.status === 'healthy' || res?.status !== 'down' ? 'up' : 'down'; - responseTime = parseInt(res?.responseTime || '0', 10) || 0; - details = res; - } else { - const errMessage = check.reason?.message || 'Unknown error'; - details = { status: 'down', error: errMessage }; - this.logger.error(`[ALERT] Health check failure: service ${serviceName} is DOWN! Error: ${errMessage}`); + return check.value; } - this.healthHistory.recordCheck({ - service: serviceName, - status, - responseTime, - timestamp: new Date(), - error: status === 'down' ? JSON.stringify(details) : undefined, - }); - - return { [serviceName]: details }; + return { + [services[index]]: { + status: 'down', + error: check.reason?.message || 'Unknown error', + }, + }; }); const totalTime = Date.now() - startTime; - const allHealthy = checks.every((c) => c.status === 'fulfilled' && (c.value as any)[Object.keys(c.value)[0]]?.status !== 'down'); + const allHealthy = checks.every((c) => c.status === 'fulfilled'); return { status: allHealthy ? 'ok' : 'degraded', @@ -174,338 +203,4 @@ export class HealthController { getStats() { return this.healthHistory.getAllStats(); } - - @Get('dashboard') - @HttpCode(HttpStatus.OK) - @Header('Content-Type', 'text/html') - @ApiOperation({ - summary: 'Get health dashboard UI', - description: 'Renders a beautiful premium HTML/CSS dashboard of service health status', - }) - async getDashboard() { - const detailed = await this.detailed(); - const stats = this.healthHistory.getAllStats(); - - // Compute some metrics for System Health indicator - const systemInfo = detailed.checks.system || {}; - const processMem = systemInfo.processMemory || { rss: '0 MB', heapUsed: '0 MB' }; - const sysMem = systemInfo.systemMemory || { total: '0 GB', free: '0 GB', utilizationPercentage: '0%' }; - const cpuInfo = systemInfo.cpu || { loadAverage1m: '0.00', cores: 1 }; - - const services = Object.keys(detailed.checks).filter(s => s !== 'system'); - - const serviceCards = services.map(name => { - const check = detailed.checks[name] || {}; - const isUp = check.status === 'up' || check.status === 'healthy' || (check.status !== 'down' && !check.error); - const resTime = check.responseTime || 'N/A'; - const svcStats = stats[name] || { uptime: '100%', avgResponseTime: '0ms' }; - const statusClass = isUp ? 'status-ok' : 'status-err'; - const badge = isUp ? 'UP' : 'DOWN'; - - let extraDetails = ''; - if (!isUp) { - extraDetails = `
${check.error || check.message || 'Unknown connection error'}
`; - } else { - extraDetails = `
Resp Time: ${resTime} | Uptime: ${svcStats.uptime}
`; - } - - return ` -
-
- ${name.toUpperCase().replace('-', ' ')} - ${badge} -
-
-
- Avg response: - ${svcStats.avgResponseTime || resTime} -
- ${extraDetails} -
-
- `; - }).join(''); - - const overallStatus = detailed.status === 'ok' ? 'ALL SYSTEMS OPERATIONAL' : 'DEGRADED PERFORMANCE'; - const overallClass = detailed.status === 'ok' ? 'status-ok' : 'status-err'; - - return ` - - - - - - Nestera Systems Health - - - - - -
-
-
-

Nestera Health Dashboard

-

Last checked: ${new Date(detailed.timestamp).toLocaleString()} | Refreshes automatically

-
-
- - ${overallStatus} -
-
- -
- ${serviceCards} -
- -
-

System & Resource Metrics

-
-
-
PROCESS MEMORY (RSS)
-
${processMem.rss}
-
Heap Used: ${processMem.heapUsed}
-
-
-
SYSTEM MEMORY UTILIZATION
-
${sysMem.utilizationPercentage}
-
Free: ${sysMem.free} / Total: ${sysMem.total}
-
-
-
CPU LOAD AVERAGE (1M)
-
${cpuInfo.loadAverage1m}
-
Cores: ${cpuInfo.cores}
-
-
-
PROCESS UPTIME
-
${systemInfo.uptime || 'N/A'}
-
Response: ${detailed.responseTime}
-
-
-
- - -
- - - `; - } } diff --git a/backend/src/modules/health/health.module.ts b/backend/src/modules/health/health.module.ts index fbecedbc2..ac4351d00 100644 --- a/backend/src/modules/health/health.module.ts +++ b/backend/src/modules/health/health.module.ts @@ -6,7 +6,6 @@ import { TypeOrmHealthIndicator } from './indicators/typeorm.health'; import { IndexerHealthIndicator } from './indicators/indexer.health'; import { RpcHealthIndicator } from './indicators/rpc.health'; import { ConnectionPoolHealthIndicator } from './indicators/connection-pool.health'; -import { SystemHealthIndicator } from './indicators/system.health'; import { RedisHealthIndicator, EmailServiceHealthIndicator, @@ -31,7 +30,6 @@ import { DeadLetterEvent } from '../blockchain/entities/dead-letter-event.entity IndexerHealthIndicator, RpcHealthIndicator, ConnectionPoolHealthIndicator, - SystemHealthIndicator, RedisHealthIndicator, EmailServiceHealthIndicator, SorobanRpcHealthIndicator, diff --git a/backend/src/modules/health/indicators/external-services.health.ts b/backend/src/modules/health/indicators/external-services.health.ts index e49b375b2..6e68b6c5e 100644 --- a/backend/src/modules/health/indicators/external-services.health.ts +++ b/backend/src/modules/health/indicators/external-services.health.ts @@ -10,9 +10,6 @@ interface ServiceHealth { error?: string; } -import * as net from 'net'; -import { URL } from 'url'; - @Injectable() export class RedisHealthIndicator extends HealthIndicator { private readonly logger = new Logger(RedisHealthIndicator.name); @@ -31,68 +28,23 @@ export class RedisHealthIndicator extends HealthIndicator { } const startTime = Date.now(); - return new Promise((resolve) => { - let host = 'localhost'; - let port = 6379; - - try { - const parsed = new URL(redisUrl); - host = parsed.hostname || 'localhost'; - port = parsed.port ? parseInt(parsed.port, 10) : 6379; - } catch (e) { - const match = redisUrl.match(/(?:redis:\/\/)?([^:/]+)(?::(\d+))?/); - if (match) { - host = match[1]; - port = match[2] ? parseInt(match[2], 10) : 6379; - } - } - - const socket = new net.Socket(); - socket.setTimeout(3000); - - const cleanup = () => { - socket.removeAllListeners(); - socket.destroy(); - }; - - socket.connect(port, host, () => { - socket.write('PING\r\n'); - }); - - socket.on('data', (data) => { - cleanup(); - const duration = Date.now() - startTime; - resolve( - this.getStatus(key, true, { - responseTime: `${duration}ms`, - }) - ); - }); + try { + // Simple ping test + const response = await axios.get(redisUrl, { timeout: 5000 }); + const responseTime = Date.now() - startTime; - socket.on('error', (err) => { - cleanup(); - const duration = Date.now() - startTime; - this.logger.error(`Redis health check failed to connect: ${err.message}`); - resolve( - this.getStatus(key, false, { - responseTime: `${duration}ms`, - error: err.message, - }) - ); + return this.getStatus(key, true, { + responseTime: `${responseTime}ms`, }); + } catch (error) { + const responseTime = Date.now() - startTime; + this.logger.error(`Redis health check failed: ${error}`); - socket.on('timeout', () => { - cleanup(); - const duration = Date.now() - startTime; - this.logger.error('Redis health check timed out'); - resolve( - this.getStatus(key, false, { - responseTime: `${duration}ms`, - error: 'Connection timeout', - }) - ); + return this.getStatus(key, false, { + responseTime: `${responseTime}ms`, + error: error instanceof Error ? error.message : 'Unknown error', }); - }); + } } } diff --git a/backend/src/modules/savings/dto/auto-deposit.dto.ts b/backend/src/modules/savings/dto/auto-deposit.dto.ts index 95a2f69f6..109b4d36c 100644 --- a/backend/src/modules/savings/dto/auto-deposit.dto.ts +++ b/backend/src/modules/savings/dto/auto-deposit.dto.ts @@ -1,31 +1,18 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNumber, IsUUID, Min } from 'class-validator'; -import { - AutoDepositFrequency, - AutoDepositStatus, -} from '../entities/auto-deposit-schedule.entity'; +import { AutoDepositFrequency, AutoDepositStatus } from '../entities/auto-deposit-schedule.entity'; export class CreateAutoDepositDto { - @ApiProperty({ - example: 'uuid-product-id', - description: 'Savings product UUID', - }) + @ApiProperty({ example: 'uuid-product-id', description: 'Savings product UUID' }) @IsUUID() productId: string; - @ApiProperty({ - example: 100, - description: 'Amount to deposit per cycle (in XLM)', - minimum: 0.01, - }) + @ApiProperty({ example: 100, description: 'Amount to deposit per cycle (in XLM)', minimum: 0.01 }) @IsNumber() @Min(0.01) amount: number; - @ApiProperty({ - enum: AutoDepositFrequency, - example: AutoDepositFrequency.MONTHLY, - }) + @ApiProperty({ enum: AutoDepositFrequency, example: AutoDepositFrequency.MONTHLY }) @IsEnum(AutoDepositFrequency) frequency: AutoDepositFrequency; } diff --git a/backend/src/modules/savings/dto/compare-products.dto.ts b/backend/src/modules/savings/dto/compare-products.dto.ts index 2a04644a7..268c4d9dc 100644 --- a/backend/src/modules/savings/dto/compare-products.dto.ts +++ b/backend/src/modules/savings/dto/compare-products.dto.ts @@ -1,13 +1,5 @@ -import { - IsArray, - IsUUID, - ArrayMinSize, - ArrayMaxSize, - IsNumber, - IsOptional, - Min, -} from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsUUID, ArrayMinSize, ArrayMaxSize } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; export class CompareProductsDto { @ApiProperty({ @@ -16,30 +8,8 @@ export class CompareProductsDto { type: [String], }) @IsArray() - @ArrayMinSize(2, { - message: 'At least 2 products are required for comparison', - }) + @ArrayMinSize(2, { message: 'At least 2 products are required for comparison' }) @ArrayMaxSize(5, { message: 'Cannot compare more than 5 products at once' }) @IsUUID('all', { each: true, message: 'Each productId must be a valid UUID' }) productIds: string[]; - - @ApiProperty({ - description: 'Investment amount to calculate projected earnings', - example: 100000, - minimum: 1, - }) - @IsNumber() - @Min(1) - amount: number; - - @ApiPropertyOptional({ - description: - 'Projected duration in months. If omitted, uses the product tenure.', - example: 12, - minimum: 1, - }) - @IsOptional() - @IsNumber() - @Min(1) - duration?: number; } diff --git a/backend/src/modules/savings/dto/create-savings-group.dto.ts b/backend/src/modules/savings/dto/create-savings-group.dto.ts index be86504b6..c1c78c500 100644 --- a/backend/src/modules/savings/dto/create-savings-group.dto.ts +++ b/backend/src/modules/savings/dto/create-savings-group.dto.ts @@ -37,36 +37,4 @@ export class CreateSavingsGroupDto { @IsNumber({}, { message: 'Target amount must be a valid number' }) @Min(1, { message: 'Target amount must be at least 1' }) targetAmount: number; - - @ApiProperty({ - example: 'prod-uuid-1', - description: 'The savings product ID this pool is based on', - }) - @IsString() - @IsNotEmpty() - productId: string; - - @ApiProperty({ - example: 'GB...XYZ', - description: 'The multisig wallet address for the group pool', - }) - @IsString() - @IsNotEmpty() - multisigAddress: string; - - @ApiProperty({ - example: 2, - description: 'Number of signatures required for withdrawals', - }) - @IsNumber() - @Min(1) - requiredSignatures: number; - - @ApiProperty({ - example: 3, - description: 'Total number of signers in the multisig setup', - }) - @IsNumber() - @Min(1) - totalSigners: number; } diff --git a/backend/src/modules/savings/dto/milestone.dto.ts b/backend/src/modules/savings/dto/milestone.dto.ts index e5eb16757..97345cc0a 100644 --- a/backend/src/modules/savings/dto/milestone.dto.ts +++ b/backend/src/modules/savings/dto/milestone.dto.ts @@ -1,11 +1,4 @@ -import { - IsInt, - IsString, - Min, - Max, - MaxLength, - IsNotEmpty, -} from 'class-validator'; +import { IsInt, IsString, Min, Max, MaxLength, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { MilestoneType } from '../entities/savings-goal-milestone.entity'; diff --git a/backend/src/modules/savings/dto/product-comparison.dto.ts b/backend/src/modules/savings/dto/product-comparison.dto.ts index c86a5754b..ab96eb5c3 100644 --- a/backend/src/modules/savings/dto/product-comparison.dto.ts +++ b/backend/src/modules/savings/dto/product-comparison.dto.ts @@ -5,10 +5,7 @@ export class HistoricalPerformanceDto { @ApiProperty({ example: 2023, description: 'Year of the performance record' }) year: number; - @ApiProperty({ - example: 10.5, - description: 'Annual return percentage for that year', - }) + @ApiProperty({ example: 10.5, description: 'Annual return percentage for that year' }) return: number; } @@ -54,11 +51,6 @@ export class ProductComparisonItemDto { description: 'Historical annual performance data', }) historicalPerformance: HistoricalPerformanceDto[]; - - @ApiProperty({ - description: 'Estimated earnings based on provided amount and duration', - }) - projectedEarnings: number; } export class ProductComparisonResponseDto { @@ -67,13 +59,4 @@ export class ProductComparisonResponseDto { @ApiProperty({ description: 'Whether this response was served from cache' }) cached: boolean; - - @ApiPropertyOptional({ - description: 'The recommended product based on earnings and risk', - example: { productId: 'uuid', reason: 'Highest projected returns' }, - }) - recommendation?: { - productId: string; - reason: string; - }; } diff --git a/backend/src/modules/savings/entities/auto-deposit-schedule.entity.ts b/backend/src/modules/savings/entities/auto-deposit-schedule.entity.ts index c2380aed7..049fc63cc 100644 --- a/backend/src/modules/savings/entities/auto-deposit-schedule.entity.ts +++ b/backend/src/modules/savings/entities/auto-deposit-schedule.entity.ts @@ -40,11 +40,7 @@ export class AutoDepositSchedule { @Column({ type: 'enum', enum: AutoDepositFrequency }) frequency: AutoDepositFrequency; - @Column({ - type: 'enum', - enum: AutoDepositStatus, - default: AutoDepositStatus.ACTIVE, - }) + @Column({ type: 'enum', enum: AutoDepositStatus, default: AutoDepositStatus.ACTIVE }) status: AutoDepositStatus; /** Next scheduled execution time */ diff --git a/backend/src/modules/savings/entities/group-savings-pool.entity.ts b/backend/src/modules/savings/entities/group-savings-pool.entity.ts index b3a201ecb..680d9c932 100644 --- a/backend/src/modules/savings/entities/group-savings-pool.entity.ts +++ b/backend/src/modules/savings/entities/group-savings-pool.entity.ts @@ -14,7 +14,6 @@ import { SavingsProduct } from './savings-product.entity'; import { GroupPoolMember } from './group-pool-member.entity'; import { MultiSigWithdrawalRequest } from './multi-sig-withdrawal-request.entity'; import { SignatureEvent } from './signature-event.entity'; -import { SavingsGroupActivity } from './savings-group-activity.entity'; export enum PoolStatus { ACTIVE = 'ACTIVE', @@ -123,9 +122,4 @@ export class GroupSavingsPool { @OneToMany(() => SignatureEvent, (event) => event.pool, { cascade: true }) signatureEvents: SignatureEvent[]; - - @OneToMany(() => SavingsGroupActivity, (activity) => activity.group, { - cascade: true, - }) - activities: SavingsGroupActivity[]; } diff --git a/backend/src/modules/savings/entities/savings-goal-milestone.entity.ts b/backend/src/modules/savings/entities/savings-goal-milestone.entity.ts index 0d15fed07..ab23727fb 100644 --- a/backend/src/modules/savings/entities/savings-goal-milestone.entity.ts +++ b/backend/src/modules/savings/entities/savings-goal-milestone.entity.ts @@ -34,11 +34,7 @@ export class SavingsGoalMilestone { @Column({ type: 'varchar', length: 255 }) label: string; - @Column({ - type: 'enum', - enum: MilestoneType, - default: MilestoneType.AUTOMATIC, - }) + @Column({ type: 'enum', enum: MilestoneType, default: MilestoneType.AUTOMATIC }) type: MilestoneType; /** Whether this milestone has been achieved */ diff --git a/backend/src/modules/savings/entities/savings-group-activity.entity.ts b/backend/src/modules/savings/entities/savings-group-activity.entity.ts index b1aafc23b..a26da33a3 100644 --- a/backend/src/modules/savings/entities/savings-group-activity.entity.ts +++ b/backend/src/modules/savings/entities/savings-group-activity.entity.ts @@ -7,7 +7,7 @@ import { JoinColumn, } from 'typeorm'; import { User } from '../../user/entities/user.entity'; -import { GroupSavingsPool } from './group-savings-pool.entity'; +import { SavingsGroup } from './savings-group.entity'; export enum SavingsGroupActivityType { CREATED = 'CREATED', @@ -44,11 +44,11 @@ export class SavingsGroupActivity { @CreateDateColumn() createdAt: Date; - @ManyToOne(() => GroupSavingsPool, (group) => group.activities, { + @ManyToOne(() => SavingsGroup, (group) => group.activities, { onDelete: 'CASCADE', }) @JoinColumn({ name: 'groupId' }) - group: GroupSavingsPool; + group: SavingsGroup; @ManyToOne(() => User) @JoinColumn({ name: 'userId' }) diff --git a/backend/src/modules/savings/entities/savings-group-member.entity.ts b/backend/src/modules/savings/entities/savings-group-member.entity.ts new file mode 100644 index 000000000..2e7155240 --- /dev/null +++ b/backend/src/modules/savings/entities/savings-group-member.entity.ts @@ -0,0 +1,56 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + Unique, +} from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { SavingsGroup } from './savings-group.entity'; + +export enum SavingsGroupRole { + ADMIN = 'ADMIN', + MEMBER = 'MEMBER', +} + +@Entity('savings_group_members') +@Unique(['groupId', 'userId']) +export class SavingsGroupMember { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column('uuid') + groupId: string; + + @Column('uuid') + userId: string; + + @Column({ + type: 'enum', + enum: SavingsGroupRole, + default: SavingsGroupRole.MEMBER, + }) + role: SavingsGroupRole; + + @Column('decimal', { precision: 14, scale: 2, default: 0 }) + contributionAmount: number; + + @CreateDateColumn() + joinedAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @ManyToOne(() => SavingsGroup, (group) => group.members, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'groupId' }) + group: SavingsGroup; + + @ManyToOne(() => User) + @JoinColumn({ name: 'userId' }) + user: User; +} diff --git a/backend/src/modules/savings/entities/savings-group.entity.ts b/backend/src/modules/savings/entities/savings-group.entity.ts new file mode 100644 index 000000000..d0ed2e457 --- /dev/null +++ b/backend/src/modules/savings/entities/savings-group.entity.ts @@ -0,0 +1,63 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + OneToMany, +} from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { SavingsGroupMember } from './savings-group-member.entity'; +import { SavingsGroupActivity } from './savings-group-activity.entity'; + +export enum SavingsGroupStatus { + OPEN = 'OPEN', + COMPLETED = 'COMPLETED', + CANCELLED = 'CANCELLED', +} + +@Entity('savings_groups') +export class SavingsGroup { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ type: 'varchar', length: 255 }) + name: string; + + @Column({ type: 'text', nullable: true }) + description: string; + + @Column('decimal', { precision: 14, scale: 2 }) + targetAmount: number; + + @Column('decimal', { precision: 14, scale: 2, default: 0 }) + currentAmount: number; + + @Column('uuid') + creatorId: string; + + @Column({ + type: 'enum', + enum: SavingsGroupStatus, + default: SavingsGroupStatus.OPEN, + }) + status: SavingsGroupStatus; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @ManyToOne(() => User) + @JoinColumn({ name: 'creatorId' }) + creator: User; + + @OneToMany(() => SavingsGroupMember, (member) => member.group) + members: SavingsGroupMember[]; + + @OneToMany(() => SavingsGroupActivity, (activity) => activity.group) + activities: SavingsGroupActivity[]; +} diff --git a/backend/src/modules/savings/entities/user-subscription.entity.ts b/backend/src/modules/savings/entities/user-subscription.entity.ts index 0053c4940..a95cd0095 100644 --- a/backend/src/modules/savings/entities/user-subscription.entity.ts +++ b/backend/src/modules/savings/entities/user-subscription.entity.ts @@ -6,7 +6,6 @@ import { UpdateDateColumn, ManyToOne, JoinColumn, - Index, } from 'typeorm'; import { SavingsProduct } from './savings-product.entity'; @@ -21,18 +20,15 @@ export class UserSubscription { @PrimaryGeneratedColumn('uuid') id: string; - @Index() @Column('uuid') userId: string; - @Index() @Column('uuid') productId: string; @Column('decimal', { precision: 14, scale: 2 }) amount: number; - @Index() @Column({ type: 'enum', enum: SubscriptionStatus, diff --git a/backend/src/modules/savings/group-savings.controller.ts b/backend/src/modules/savings/group-savings.controller.ts index 45f5e9352..4cf60cc8d 100644 --- a/backend/src/modules/savings/group-savings.controller.ts +++ b/backend/src/modules/savings/group-savings.controller.ts @@ -21,8 +21,8 @@ import { CurrentUser } from '../../common/decorators/current-user.decorator'; import { CreateSavingsGroupDto } from './dto/create-savings-group.dto'; import { ContributeSavingsGroupDto } from './dto/contribute-savings-group.dto'; import { InviteMemberDto } from './dto/invite-member.dto'; -import { GroupSavingsPool } from './entities/group-savings-pool.entity'; -import { GroupPoolMember } from './entities/group-pool-member.entity'; +import { SavingsGroup } from './entities/savings-group.entity'; +import { SavingsGroupMember } from './entities/savings-group-member.entity'; import { SavingsGroupActivity } from './entities/savings-group-activity.entity'; @ApiTags('savings/groups') @@ -38,12 +38,12 @@ export class GroupSavingsController { @ApiResponse({ status: 201, description: 'Group created successfully', - type: GroupSavingsPool, + type: SavingsGroup, }) async createGroup( @CurrentUser() user: { id: string }, @Body() dto: CreateSavingsGroupDto, - ): Promise<{ success: boolean; data: GroupSavingsPool }> { + ): Promise<{ success: boolean; data: SavingsGroup }> { const data = await this.groupSavingsService.createGroup(user.id, dto); return { success: true, data }; } @@ -55,12 +55,12 @@ export class GroupSavingsController { @ApiResponse({ status: 200, description: 'Joined group successfully', - type: GroupPoolMember, + type: SavingsGroupMember, }) async joinGroup( @CurrentUser() user: { id: string }, @Param('id') id: string, - ): Promise<{ success: boolean; data: GroupPoolMember }> { + ): Promise<{ success: boolean; data: SavingsGroupMember }> { const data = await this.groupSavingsService.joinGroup(user.id, id); return { success: true, data }; } @@ -72,13 +72,13 @@ export class GroupSavingsController { @ApiResponse({ status: 200, description: 'User invited successfully', - type: GroupPoolMember, + type: SavingsGroupMember, }) async inviteMember( @CurrentUser() user: { id: string }, @Param('id') id: string, @Body() dto: InviteMemberDto, - ): Promise<{ success: boolean; data: GroupPoolMember }> { + ): Promise<{ success: boolean; data: SavingsGroupMember }> { const data = await this.groupSavingsService.inviteMember(user.id, id, dto); return { success: true, data }; } @@ -89,11 +89,11 @@ export class GroupSavingsController { @ApiResponse({ status: 200, description: 'List of members', - type: [GroupPoolMember], + type: [SavingsGroupMember], }) async listMembers( @Param('id') id: string, - ): Promise<{ success: boolean; data: GroupPoolMember[] }> { + ): Promise<{ success: boolean; data: SavingsGroupMember[] }> { const data = await this.groupSavingsService.listMembers(id); return { success: true, data }; } @@ -105,13 +105,13 @@ export class GroupSavingsController { @ApiResponse({ status: 200, description: 'Contribution successful', - type: GroupSavingsPool, + type: SavingsGroup, }) async contribute( @CurrentUser() user: { id: string }, @Param('id') id: string, @Body() dto: ContributeSavingsGroupDto, - ): Promise<{ success: boolean; data: GroupSavingsPool }> { + ): Promise<{ success: boolean; data: SavingsGroup }> { const data = await this.groupSavingsService.contribute(user.id, id, dto); return { success: true, data }; } diff --git a/backend/src/modules/savings/group-savings.service.spec.ts b/backend/src/modules/savings/group-savings.service.spec.ts index 310bf1ff4..946e22042 100644 --- a/backend/src/modules/savings/group-savings.service.spec.ts +++ b/backend/src/modules/savings/group-savings.service.spec.ts @@ -3,13 +3,13 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { GroupSavingsService } from './group-savings.service'; import { - GroupSavingsPool, - PoolStatus, -} from './entities/group-savings-pool.entity'; + SavingsGroup, + SavingsGroupStatus, +} from './entities/savings-group.entity'; import { - GroupPoolMember, - MemberRole, -} from './entities/group-pool-member.entity'; + SavingsGroupMember, + SavingsGroupRole, +} from './entities/savings-group-member.entity'; import { SavingsGroupActivity, SavingsGroupActivityType, @@ -55,11 +55,11 @@ describe('GroupSavingsService', () => { providers: [ GroupSavingsService, { - provide: getRepositoryToken(GroupSavingsPool), + provide: getRepositoryToken(SavingsGroup), useValue: groupRepository, }, { - provide: getRepositoryToken(GroupPoolMember), + provide: getRepositoryToken(SavingsGroupMember), useValue: memberRepository, }, { @@ -74,45 +74,38 @@ describe('GroupSavingsService', () => { }); describe('createGroup', () => { - it('should create a group pool and add creator as owner', async () => { - const dto = { - name: 'Test Pool', - targetAmount: 1000, - productId: 'prod-1', - multisigAddress: 'G...XYZ', - requiredSignatures: 2, - totalSigners: 3, - }; + it('should create a group and add creator as admin', async () => { + const dto = { name: 'Test Group', targetAmount: 1000 }; const userId = 'user-1'; const result = await service.createGroup(userId, dto); expect(dataSource.transaction).toHaveBeenCalled(); - expect(result.name).toBe('Test Pool'); + expect(result.name).toBe('Test Group'); expect(result.creatorId).toBe(userId); }); }); describe('joinGroup', () => { - it('should allow a user to join an active group pool', async () => { + it('should allow a user to join an open group', async () => { const userId = 'user-2'; const groupId = 'group-1'; groupRepository.findOneBy.mockResolvedValue({ id: groupId, - status: PoolStatus.ACTIVE, + status: SavingsGroupStatus.OPEN, }); memberRepository.findOneBy.mockResolvedValue(null); const result = await service.joinGroup(userId, groupId); expect(result.userId).toBe(userId); - expect(result.poolId).toBe(groupId); + expect(result.groupId).toBe(groupId); }); it('should throw ConflictException if already a member', async () => { groupRepository.findOneBy.mockResolvedValue({ id: 'group-1', - status: PoolStatus.ACTIVE, + status: SavingsGroupStatus.OPEN, }); memberRepository.findOneBy.mockResolvedValue({ id: 'member-1' }); @@ -133,21 +126,21 @@ describe('GroupSavingsService', () => { memberRepository.findOneBy .mockResolvedValueOnce({ id: 'admin-member', - role: MemberRole.OWNER, + role: SavingsGroupRole.ADMIN, }) .mockResolvedValueOnce(null); const result = await service.inviteMember(adminId, groupId, dto); expect(result.userId).toBe(targetUserId); - expect(result.role).toBe(MemberRole.MEMBER); + expect(result.role).toBe(SavingsGroupRole.MEMBER); }); it('should throw ForbiddenException if non-admin invites', async () => { groupRepository.findOneBy.mockResolvedValue({ id: 'group-1' }); memberRepository.findOneBy.mockResolvedValue({ id: 'member-1', - role: MemberRole.MEMBER, + role: SavingsGroupRole.MEMBER, }); await expect( @@ -157,60 +150,56 @@ describe('GroupSavingsService', () => { }); describe('contribute', () => { - it('should update group pool and member contribution amounts', async () => { + it('should update group and member amounts', async () => { const userId = 'user-1'; const groupId = 'group-1'; const dto = { amount: 100 }; groupRepository.findOneBy.mockResolvedValue({ id: groupId, - status: PoolStatus.ACTIVE, - currentBalance: 0, + status: SavingsGroupStatus.OPEN, + currentAmount: 0, targetAmount: 1000, }); memberRepository.findOneBy.mockResolvedValue({ - poolId: groupId, + groupId, userId, - totalContributed: 0, + contributionAmount: 0, }); const result = await service.contribute(userId, groupId, dto); - expect(Number(result.currentBalance)).toBe(100); + expect(Number(result.currentAmount)).toBe(100); }); - it('should track cumulative contributions correctly', async () => { + it('should complete group if target reached', async () => { groupRepository.findOneBy.mockResolvedValue({ id: 'group-1', - status: PoolStatus.ACTIVE, - currentBalance: 950, + status: SavingsGroupStatus.OPEN, + currentAmount: 950, targetAmount: 1000, }); - memberRepository.findOneBy.mockResolvedValue({ - poolId: 'group-1', - totalContributed: 200, - }); + memberRepository.findOneBy.mockResolvedValue({ contributionAmount: 0 }); const result = await service.contribute('user-1', 'group-1', { amount: 50, }); - expect(Number(result.currentBalance)).toBe(1000); + expect(result.status).toBe(SavingsGroupStatus.COMPLETED); }); }); describe('leaveGroup', () => { - it('should refund user and remove member from pool', async () => { + it('should refund user and remove member', async () => { const userId = 'user-1'; const groupId = 'group-1'; groupRepository.findOneBy.mockResolvedValue({ id: groupId, - currentBalance: 500, - totalDeposits: 500, + currentAmount: 500, }); memberRepository.findOneBy.mockResolvedValue({ id: 'member-1', - totalContributed: 100, + contributionAmount: 100, }); const result = await service.leaveGroup(userId, groupId); diff --git a/backend/src/modules/savings/group-savings.service.ts b/backend/src/modules/savings/group-savings.service.ts index bf9ceec18..16384517b 100644 --- a/backend/src/modules/savings/group-savings.service.ts +++ b/backend/src/modules/savings/group-savings.service.ts @@ -8,14 +8,13 @@ import { import { InjectRepository } from '@nestjs/typeorm'; import { Repository, DataSource } from 'typeorm'; import { - GroupSavingsPool, - PoolStatus, -} from './entities/group-savings-pool.entity'; + SavingsGroup, + SavingsGroupStatus, +} from './entities/savings-group.entity'; import { - GroupPoolMember, - MemberRole, - MemberStatus, -} from './entities/group-pool-member.entity'; + SavingsGroupMember, + SavingsGroupRole, +} from './entities/savings-group-member.entity'; import { SavingsGroupActivity, SavingsGroupActivityType, @@ -27,10 +26,10 @@ import { InviteMemberDto } from './dto/invite-member.dto'; @Injectable() export class GroupSavingsService { constructor( - @InjectRepository(GroupSavingsPool) - private readonly groupRepository: Repository, - @InjectRepository(GroupPoolMember) - private readonly memberRepository: Repository, + @InjectRepository(SavingsGroup) + private readonly groupRepository: Repository, + @InjectRepository(SavingsGroupMember) + private readonly memberRepository: Repository, @InjectRepository(SavingsGroupActivity) private readonly activityRepository: Repository, private readonly dataSource: DataSource, @@ -39,26 +38,21 @@ export class GroupSavingsService { async createGroup( creatorId: string, dto: CreateSavingsGroupDto, - ): Promise { + ): Promise { return await this.dataSource.transaction(async (manager) => { - const group = manager.create(GroupSavingsPool, { + const group = manager.create(SavingsGroup, { ...dto, creatorId, - currentBalance: 0, - totalDeposits: 0, - status: PoolStatus.ACTIVE, + currentAmount: 0, + status: SavingsGroupStatus.OPEN, }); const savedGroup = await manager.save(group); - const member = manager.create(GroupPoolMember, { - poolId: savedGroup.id, + const member = manager.create(SavingsGroupMember, { + groupId: savedGroup.id, userId: creatorId, - role: MemberRole.OWNER, - walletAddress: dto.multisigAddress, // For creator, we use pool multisig as primary or their wallet - status: MemberStatus.ACTIVE, - totalContributed: 0, - sharePercentage: 100, - joinedAt: new Date(), + role: SavingsGroupRole.ADMIN, + contributionAmount: 0, }); await manager.save(member); @@ -74,31 +68,30 @@ export class GroupSavingsService { }); } - async joinGroup(userId: string, groupId: string): Promise { + async joinGroup( + userId: string, + groupId: string, + ): Promise { const group = await this.groupRepository.findOneBy({ id: groupId }); - if (!group) throw new NotFoundException('Savings group pool not found'); - if (group.status !== PoolStatus.ACTIVE) { - throw new BadRequestException('Group pool is not active for joining'); + if (!group) throw new NotFoundException('Savings group not found'); + if (group.status !== SavingsGroupStatus.OPEN) { + throw new BadRequestException('Group is not open for joining'); } const existingMember = await this.memberRepository.findOneBy({ - poolId: groupId, + groupId, userId, }); if (existingMember) { - throw new ConflictException('User is already a member of this pool'); + throw new ConflictException('User is already a member of this group'); } return await this.dataSource.transaction(async (manager) => { - const member = manager.create(GroupPoolMember, { - poolId: groupId, + const member = manager.create(SavingsGroupMember, { + groupId, userId, - role: MemberRole.MEMBER, - status: MemberStatus.ACTIVE, - totalContributed: 0, - sharePercentage: 0, - walletAddress: '', // Should be provided by user in real scenario - joinedAt: new Date(), + role: SavingsGroupRole.MEMBER, + contributionAmount: 0, }); const savedMember = await manager.save(member); @@ -117,43 +110,33 @@ export class GroupSavingsService { adminId: string, groupId: string, dto: InviteMemberDto, - ): Promise { + ): Promise { const group = await this.groupRepository.findOneBy({ id: groupId }); - if (!group) throw new NotFoundException('Savings group pool not found'); + if (!group) throw new NotFoundException('Savings group not found'); const adminMember = await this.memberRepository.findOneBy({ - poolId: groupId, + groupId, userId: adminId, }); - if ( - !adminMember || - (adminMember.role !== MemberRole.ADMIN && - adminMember.role !== MemberRole.OWNER) - ) { - throw new ForbiddenException( - 'Only group admins or owners can invite members', - ); + if (!adminMember || adminMember.role !== SavingsGroupRole.ADMIN) { + throw new ForbiddenException('Only group admins can invite members'); } const targetUserId = dto.userId; const existingMember = await this.memberRepository.findOneBy({ - poolId: groupId, + groupId, userId: targetUserId, }); if (existingMember) { - throw new ConflictException('User is already a member of this pool'); + throw new ConflictException('User is already a member of this group'); } return await this.dataSource.transaction(async (manager) => { - const member = manager.create(GroupPoolMember, { - poolId: groupId, + const member = manager.create(SavingsGroupMember, { + groupId, userId: targetUserId, - role: MemberRole.MEMBER, - status: MemberStatus.ACTIVE, - totalContributed: 0, - sharePercentage: 0, - walletAddress: '', // To be updated by user on join - joinedAt: new Date(), + role: SavingsGroupRole.MEMBER, + contributionAmount: 0, }); const savedMember = await manager.save(member); @@ -169,12 +152,12 @@ export class GroupSavingsService { }); } - async listMembers(groupId: string): Promise { + async listMembers(groupId: string): Promise { const group = await this.groupRepository.findOneBy({ id: groupId }); - if (!group) throw new NotFoundException('Savings group pool not found'); + if (!group) throw new NotFoundException('Savings group not found'); return await this.memberRepository.find({ - where: { poolId: groupId }, + where: { groupId }, relations: ['user'], order: { joinedAt: 'ASC' }, }); @@ -184,40 +167,31 @@ export class GroupSavingsService { userId: string, groupId: string, dto: ContributeSavingsGroupDto, - ): Promise { + ): Promise { const group = await this.groupRepository.findOneBy({ id: groupId }); - if (!group) throw new NotFoundException('Savings group pool not found'); - if (group.status !== PoolStatus.ACTIVE) { - throw new BadRequestException( - 'Group pool is not accepting contributions', - ); + if (!group) throw new NotFoundException('Savings group not found'); + if (group.status !== SavingsGroupStatus.OPEN) { + throw new BadRequestException('Group is not accepting contributions'); } - const member = await this.memberRepository.findOneBy({ - poolId: groupId, - userId, - }); + const member = await this.memberRepository.findOneBy({ groupId, userId }); if (!member) { - throw new ForbiddenException('Only pool members can contribute'); + throw new ForbiddenException('Only group members can contribute'); } return await this.dataSource.transaction(async (manager) => { const amount = Number(dto.amount); // Update member contribution - member.totalContributed = Number(member.totalContributed) + amount; + member.contributionAmount = Number(member.contributionAmount) + amount; await manager.save(member); - // Update pool total - group.totalDeposits = Number(group.totalDeposits) + amount; - group.currentBalance = Number(group.currentBalance) + amount; + // Update group total + group.currentAmount = Number(group.currentAmount) + amount; - // Check if target reached - if ( - group.targetAmount && - Number(group.currentBalance) >= Number(group.targetAmount) - ) { - // Pool logic might differ, but for now we keep it active or mark closed + // Check if goal reached + if (Number(group.currentAmount) >= Number(group.targetAmount)) { + group.status = SavingsGroupStatus.COMPLETED; } const savedGroup = await manager.save(group); @@ -251,21 +225,22 @@ export class GroupSavingsService { groupId: string, ): Promise<{ success: boolean; refundAmount: number }> { const group = await this.groupRepository.findOneBy({ id: groupId }); - if (!group) throw new NotFoundException('Savings group pool not found'); + if (!group) throw new NotFoundException('Savings group not found'); - const member = await this.memberRepository.findOneBy({ - poolId: groupId, - userId, - }); - if (!member) throw new NotFoundException('Pool membership not found'); + const member = await this.memberRepository.findOneBy({ groupId, userId }); + if (!member) throw new NotFoundException('Membership not found'); return await this.dataSource.transaction(async (manager) => { - const refundAmount = Number(member.totalContributed); - - // Update pool amount - group.totalDeposits = Number(group.totalDeposits) - refundAmount; - group.currentBalance = Number(group.currentBalance) - refundAmount; + const refundAmount = Number(member.contributionAmount); + // Update group amount + group.currentAmount = Number(group.currentAmount) - refundAmount; + if ( + group.status === SavingsGroupStatus.COMPLETED && + Number(group.currentAmount) < Number(group.targetAmount) + ) { + group.status = SavingsGroupStatus.OPEN; + } await manager.save(group); // Record refund activity diff --git a/backend/src/modules/savings/savings.compare.spec.ts b/backend/src/modules/savings/savings.compare.spec.ts index 355d94ac9..904772e30 100644 --- a/backend/src/modules/savings/savings.compare.spec.ts +++ b/backend/src/modules/savings/savings.compare.spec.ts @@ -182,22 +182,22 @@ describe('SavingsService – compareProducts', () => { ]; productRepository.find.mockResolvedValue(products); - const result = await service.compareProducts(['prod-1', 'prod-2'], 1000); + const result = await service.compareProducts(['prod-1', 'prod-2']); expect(result.cached).toBe(false); expect(result.products).toHaveLength(2); expect(result.products[0]).toMatchObject({ id: 'prod-1', apy: 8, - projectedEarnings: expect.any(Number), + riskLevel: 'medium', + tenure: null, }); expect(result.products[1]).toMatchObject({ id: 'prod-2', apy: 12, - projectedEarnings: expect.any(Number), + riskLevel: 'low', + tenure: 12, }); - expect(result.recommendation).toBeDefined(); - expect(result.recommendation?.productId).toBe('prod-2'); }); it('includes historical performance data for each product', async () => { @@ -206,30 +206,33 @@ describe('SavingsService – compareProducts', () => { makeProduct({ id: 'prod-2', interestRate: 10 }), ]); - const result = await service.compareProducts(['prod-1', 'prod-2'], 1000); + const result = await service.compareProducts(['prod-1', 'prod-2']); for (const product of result.products) { expect(product.historicalPerformance).toHaveLength(2); + expect(product.historicalPerformance[0]).toHaveProperty('year'); + expect(product.historicalPerformance[0]).toHaveProperty('return'); } }); it('returns cached: true when result is in cache', async () => { const cachedResponse = { - products: [], + products: [makeProduct() as any], cached: false, }; cacheManager.get.mockResolvedValue(cachedResponse); - const result = await service.compareProducts(['prod-1', 'prod-2'], 1000); + const result = await service.compareProducts(['prod-1', 'prod-2']); expect(result.cached).toBe(true); + expect(productRepository.find).not.toHaveBeenCalled(); }); it('throws NotFoundException when a product ID does not exist', async () => { productRepository.find.mockResolvedValue([makeProduct({ id: 'prod-1' })]); await expect( - service.compareProducts(['prod-1', 'prod-missing'], 1000), + service.compareProducts(['prod-1', 'prod-missing']), ).rejects.toThrow(NotFoundException); }); @@ -239,8 +242,12 @@ describe('SavingsService – compareProducts', () => { makeProduct({ id: 'prod-2' }), ]); - await service.compareProducts(['prod-1', 'prod-2'], 1000); + await service.compareProducts(['prod-1', 'prod-2']); - expect(cacheManager.set).toHaveBeenCalled(); + expect(cacheManager.set).toHaveBeenCalledWith( + expect.stringContaining('compare:'), + expect.objectContaining({ cached: false }), + expect.any(Number), + ); }); }); diff --git a/backend/src/modules/savings/savings.controller.ts b/backend/src/modules/savings/savings.controller.ts index 2a4d99c4b..13ad86890 100644 --- a/backend/src/modules/savings/savings.controller.ts +++ b/backend/src/modules/savings/savings.controller.ts @@ -169,11 +169,7 @@ export class SavingsController { async compareProducts( @Body() dto: CompareProductsDto, ): Promise { - return this.savingsService.compareProducts( - dto.productIds, - dto.amount, - dto.duration, - ); + return this.savingsService.compareProducts(dto.productIds); } @Get('products/:id/metrics') diff --git a/backend/src/modules/savings/savings.module.ts b/backend/src/modules/savings/savings.module.ts index 9e2d1b00d..7c6ece37b 100644 --- a/backend/src/modules/savings/savings.module.ts +++ b/backend/src/modules/savings/savings.module.ts @@ -21,8 +21,8 @@ import { WaitlistController } from './waitlist.controller'; import { SavingsExperiment } from './entities/savings-experiment.entity'; import { SavingsExperimentAssignment } from './entities/savings-experiment-assignment.entity'; import { ExperimentsService } from './experiments.service'; -import { GroupSavingsPool } from './entities/group-savings-pool.entity'; -import { GroupPoolMember } from './entities/group-pool-member.entity'; +import { SavingsGroup } from './entities/savings-group.entity'; +import { SavingsGroupMember } from './entities/savings-group-member.entity'; import { SavingsGroupActivity } from './entities/savings-group-activity.entity'; import { GroupSavingsService } from './group-savings.service'; import { GroupSavingsController } from './group-savings.controller'; @@ -45,8 +45,8 @@ import { AutoDepositService } from './services/auto-deposit.service'; WaitlistEvent, SavingsExperiment, SavingsExperimentAssignment, - GroupSavingsPool, - GroupPoolMember, + SavingsGroup, + SavingsGroupMember, SavingsGroupActivity, AutoDepositSchedule, ]), diff --git a/backend/src/modules/savings/savings.service.spec.ts b/backend/src/modules/savings/savings.service.spec.ts index 046ba09fb..a1af9cc4a 100644 --- a/backend/src/modules/savings/savings.service.spec.ts +++ b/backend/src/modules/savings/savings.service.spec.ts @@ -30,7 +30,7 @@ describe('SavingsService', () => { getUserSavingsBalance: jest.Mock; getUserVaultBalance: jest.Mock; }; - let cacheManager: { del: jest.Mock; get: jest.Mock; set: jest.Mock }; + let cacheManager: { del: jest.Mock }; beforeEach(async () => { productRepository = { @@ -60,8 +60,6 @@ describe('SavingsService', () => { cacheManager = { del: jest.fn(), - get: jest.fn().mockResolvedValue(undefined), - set: jest.fn().mockResolvedValue(undefined), }; const module: TestingModule = await Test.createTestingModule({ diff --git a/backend/src/modules/savings/savings.service.ts b/backend/src/modules/savings/savings.service.ts index e2adb8961..13b719b66 100644 --- a/backend/src/modules/savings/savings.service.ts +++ b/backend/src/modules/savings/savings.service.ts @@ -262,7 +262,7 @@ export class SavingsService { .filter((s) => s.status === SubscriptionStatus.ACTIVE) .reduce((sum, s) => sum + Number(s.amount), 0) : 0; - const capacity = await this.getProductCapacitySnapshot(product.id, product); + const capacity = await this.getProductCapacitySnapshot(product.id); return { id: product.id, @@ -316,7 +316,7 @@ export class SavingsService { */ async compareProducts( productIds: string[], - amount: number, + amount?: number, duration?: number, ): Promise { const cacheKey = `compare:${[...productIds].sort().join(',')}:${amount ?? ''}:${duration ?? ''}`; @@ -339,53 +339,26 @@ export class SavingsService { ); } - const items: ProductComparisonItemDto[] = products.map((product) => { - const apy = Number(product.interestRate); - const productDuration = duration ?? product.tenureMonths ?? 12; - - const projectedEarnings = this.calculateProjectedEarnings( - amount, - apy, - productDuration, - ); - - return { - id: product.id, - name: product.name, - type: product.type, - description: product.description, - apy, - tenure: product.tenureMonths, - riskLevel: deriveRiskLevel(product.type), - minAmount: Number(product.minAmount), - maxAmount: Number(product.maxAmount), - isActive: product.isActive, - contractId: product.contractId, - historicalPerformance: buildHistoricalPerformance(apy), - projectedEarnings, - }; - }); - - // Recommendation logic: Highest projected return, considering risk level as tie-breaker - const recommendedProduct = [...items].sort((a, b) => { - // Primary: Projected Earnings (Descending) - if (b.projectedEarnings !== a.projectedEarnings) { - return b.projectedEarnings - a.projectedEarnings; - } - // Secondary: Risk Level (Ascending: low < medium < high) - const riskOrder = { low: 0, medium: 1, high: 2 }; - return riskOrder[a.riskLevel] - riskOrder[b.riskLevel]; - })[0]; + const items: ProductComparisonItemDto[] = products.map((product) => ({ + id: product.id, + name: product.name, + type: product.type, + description: product.description, + apy: Number(product.interestRate), + tenure: product.tenureMonths, + riskLevel: deriveRiskLevel(product.type), + minAmount: Number(product.minAmount), + maxAmount: Number(product.maxAmount), + isActive: product.isActive, + contractId: product.contractId, + historicalPerformance: buildHistoricalPerformance( + Number(product.interestRate), + ), + })); const response: ProductComparisonResponseDto = { products: items, cached: false, - recommendation: recommendedProduct - ? { - productId: recommendedProduct.id, - reason: `Based on your ${amount} XLM investment, ${recommendedProduct.name} offers the best balance of projected returns (${recommendedProduct.projectedEarnings} XLM after 1% fee) and ${recommendedProduct.riskLevel} risk over ${duration ?? recommendedProduct.tenure ?? 12} months.`, - } - : undefined, }; await this.cacheManager.set(cacheKey, response, COMPARE_CACHE_TTL_MS); @@ -600,90 +573,41 @@ export class SavingsService { } const userPublicKey = user.publicKey; + const defaultVaultContractId = this.configService.get('stellar.contractId') || null; - // Group subscriptions by vault contract ID for batching - const subscriptionsByVault = new Map(); - for (const sub of subscriptions) { - const vaultId = - this.resolveVaultContractId(sub) ?? defaultVaultContractId; - if (!subscriptionsByVault.has(vaultId)) { - subscriptionsByVault.set(vaultId, []); - } - subscriptionsByVault.get(vaultId)!.push(sub); - } - - // Batch RPC calls per vault contract ID - const balancesByContractAndUser = new Map>(); - - await Promise.all( - Array.from(subscriptionsByVault.entries()).map( - async ([vaultContractId, vaultSubs]) => { - if (!vaultContractId) { - return; - } - - // Check cache first - const cacheKey = `vault_balance:${vaultContractId}:${userPublicKey}`; - const cached = await this.cacheManager.get(cacheKey); - - if (cached !== undefined) { - if (!balancesByContractAndUser.has(vaultContractId)) { - balancesByContractAndUser.set(vaultContractId, new Map()); - } - balancesByContractAndUser - .get(vaultContractId)! - .set(userPublicKey, cached); - return; - } - - // Fetch balance and cache it (TTL: 5 minutes) - const balance = - await this.blockchainSavingsService.getUserVaultBalance( - vaultContractId, - userPublicKey, - ); - - await this.cacheManager.set(cacheKey, balance, 5 * 60 * 1000); - - if (!balancesByContractAndUser.has(vaultContractId)) { - balancesByContractAndUser.set(vaultContractId, new Map()); - } - balancesByContractAndUser - .get(vaultContractId)! - .set(userPublicKey, balance); - }, - ), - ); + return await Promise.all( + subscriptions.map(async (subscription) => { + const fallbackAmount = Number(subscription.amount); + const vaultContractId = + this.resolveVaultContractId(subscription) ?? defaultVaultContractId; + + if (!vaultContractId) { + return this.mapSubscriptionWithLiveBalance( + subscription, + fallbackAmount, + Math.round(fallbackAmount * STROOPS_PER_XLM), + 'cache', + null, + ); + } - // Map results - return subscriptions.map((subscription) => { - const vaultContractId = - this.resolveVaultContractId(subscription) ?? defaultVaultContractId; - const fallbackAmount = Number(subscription.amount); + const liveBalanceStroops = + await this.blockchainSavingsService.getUserVaultBalance( + vaultContractId, + userPublicKey, + ); - if (!vaultContractId) { return this.mapSubscriptionWithLiveBalance( subscription, - fallbackAmount, - Math.round(fallbackAmount * STROOPS_PER_XLM), - 'cache', - null, + this.stroopsToDecimal(liveBalanceStroops), + liveBalanceStroops, + 'rpc', + vaultContractId, ); - } - - const liveBalanceStroops = - balancesByContractAndUser.get(vaultContractId)?.get(userPublicKey) ?? 0; - - return this.mapSubscriptionWithLiveBalance( - subscription, - this.stroopsToDecimal(liveBalanceStroops), - liveBalanceStroops, - 'rpc', - vaultContractId, - ); - }); + }), + ); } async getProductMetrics( @@ -899,9 +823,8 @@ export class SavingsService { async getProductCapacitySnapshot( productId: string, - preFetchedProduct?: SavingsProduct, ): Promise { - const product = preFetchedProduct || await this.findOneProduct(productId); + const product = await this.findOneProduct(productId); const maxCapacity = product.maxCapacity != null ? Number(product.maxCapacity) @@ -928,21 +851,15 @@ export class SavingsService { } if (source === 'database') { - if (product.subscriptions) { - utilizedCapacity = product.subscriptions - .filter((s) => s.status === SubscriptionStatus.ACTIVE) - .reduce((sum, s) => sum + Number(s.amount), 0); - } else { - const total = await this.subscriptionRepository - .createQueryBuilder('subscription') - .select('COALESCE(SUM(subscription.amount), 0)', 'total') - .where('subscription.productId = :productId', { productId: product.id }) - .andWhere('subscription.status = :status', { - status: SubscriptionStatus.ACTIVE, - }) - .getRawOne<{ total: string }>(); - utilizedCapacity = Number(total?.total ?? 0); - } + const total = await this.subscriptionRepository + .createQueryBuilder('subscription') + .select('COALESCE(SUM(subscription.amount), 0)', 'total') + .where('subscription.productId = :productId', { productId }) + .andWhere('subscription.status = :status', { + status: SubscriptionStatus.ACTIVE, + }) + .getRawOne<{ total: string }>(); + utilizedCapacity = Number(total?.total ?? 0); } const availableCapacity = @@ -1511,30 +1428,4 @@ export class SavingsService { return product; } - - private calculateProjectedEarnings( - amount: number, - apy: number, - durationMonths: number, - ): number { - const rateDecimal = apy / 100; - const compoundingPeriodsPerYear = 12; - const timeInYears = durationMonths / 12; - - // A = P(1 + r/n)^(nt) - const projectedBalance = - amount * - Math.pow( - 1 + rateDecimal / compoundingPeriodsPerYear, - compoundingPeriodsPerYear * timeInYears, - ); - - const earnings = projectedBalance - amount; - - // Applying a 1% protocol fee impact as assumed in AdminAnalyticsService - const netEarnings = earnings * 0.99; - - // Round to 7 decimals (Stellar precision) - return Number(netEarnings.toFixed(7)); - } } diff --git a/backend/src/modules/savings/services/auto-deposit.service.spec.ts b/backend/src/modules/savings/services/auto-deposit.service.spec.ts index 23286d062..18b5965b8 100644 --- a/backend/src/modules/savings/services/auto-deposit.service.spec.ts +++ b/backend/src/modules/savings/services/auto-deposit.service.spec.ts @@ -43,22 +43,14 @@ describe('AutoDepositService', () => { describe('create', () => { it('creates a schedule with correct nextRunAt', async () => { - const dto = { - productId: 'prod-1', - amount: 50, - frequency: AutoDepositFrequency.MONTHLY, - }; + const dto = { productId: 'prod-1', amount: 50, frequency: AutoDepositFrequency.MONTHLY }; const saved = { id: 'sched-1', ...dto, status: AutoDepositStatus.ACTIVE }; repo.save.mockResolvedValue(saved); const result = await service.create('user-1', dto); expect(repo.create).toHaveBeenCalledWith( - expect.objectContaining({ - userId: 'user-1', - productId: 'prod-1', - amount: 50, - }), + expect.objectContaining({ userId: 'user-1', productId: 'prod-1', amount: 50 }), ); expect(result).toEqual(saved); }); @@ -66,16 +58,9 @@ describe('AutoDepositService', () => { describe('pause', () => { it('pauses an active schedule', async () => { - const schedule = { - id: 's1', - userId: 'u1', - status: AutoDepositStatus.ACTIVE, - }; + const schedule = { id: 's1', userId: 'u1', status: AutoDepositStatus.ACTIVE }; repo.findOne.mockResolvedValue(schedule); - repo.save.mockResolvedValue({ - ...schedule, - status: AutoDepositStatus.PAUSED, - }); + repo.save.mockResolvedValue({ ...schedule, status: AutoDepositStatus.PAUSED }); const result = await service.pause('s1', 'u1'); expect(result.status).toBe(AutoDepositStatus.PAUSED); @@ -83,40 +68,23 @@ describe('AutoDepositService', () => { it('throws when schedule not found', async () => { repo.findOne.mockResolvedValue(null); - await expect(service.pause('bad-id', 'u1')).rejects.toThrow( - NotFoundException, - ); + await expect(service.pause('bad-id', 'u1')).rejects.toThrow(NotFoundException); }); it('throws when trying to pause a cancelled schedule', async () => { - repo.findOne.mockResolvedValue({ - id: 's1', - userId: 'u1', - status: AutoDepositStatus.CANCELLED, - }); - await expect(service.pause('s1', 'u1')).rejects.toThrow( - BadRequestException, - ); + repo.findOne.mockResolvedValue({ id: 's1', userId: 'u1', status: AutoDepositStatus.CANCELLED }); + await expect(service.pause('s1', 'u1')).rejects.toThrow(BadRequestException); }); }); describe('cancel', () => { it('cancels a schedule', async () => { - const schedule = { - id: 's1', - userId: 'u1', - status: AutoDepositStatus.ACTIVE, - }; + const schedule = { id: 's1', userId: 'u1', status: AutoDepositStatus.ACTIVE }; repo.findOne.mockResolvedValue(schedule); - repo.save.mockResolvedValue({ - ...schedule, - status: AutoDepositStatus.CANCELLED, - }); + repo.save.mockResolvedValue({ ...schedule, status: AutoDepositStatus.CANCELLED }); await service.cancel('s1', 'u1'); - expect(repo.save).toHaveBeenCalledWith( - expect.objectContaining({ status: AutoDepositStatus.CANCELLED }), - ); + expect(repo.save).toHaveBeenCalledWith(expect.objectContaining({ status: AutoDepositStatus.CANCELLED })); }); }); diff --git a/backend/src/modules/savings/services/auto-deposit.service.ts b/backend/src/modules/savings/services/auto-deposit.service.ts index 07d37bac8..2ff17e538 100644 --- a/backend/src/modules/savings/services/auto-deposit.service.ts +++ b/backend/src/modules/savings/services/auto-deposit.service.ts @@ -27,10 +27,7 @@ export class AutoDepositService { private readonly savingsService: SavingsService, ) {} - async create( - userId: string, - dto: CreateAutoDepositDto, - ): Promise { + async create(userId: string, dto: CreateAutoDepositDto): Promise { const nextRunAt = this.computeNextRun(dto.frequency); const schedule = this.scheduleRepo.create({ userId, @@ -111,10 +108,7 @@ export class AutoDepositService { } } - private async findOwned( - id: string, - userId: string, - ): Promise { + private async findOwned(id: string, userId: string): Promise { const schedule = await this.scheduleRepo.findOne({ where: { id, userId } }); if (!schedule) { throw new NotFoundException(`Auto-deposit schedule ${id} not found`); diff --git a/backend/src/modules/transactions/transactions.controller.ts b/backend/src/modules/transactions/transactions.controller.ts index 3868b65ee..7e58e3ce2 100644 --- a/backend/src/modules/transactions/transactions.controller.ts +++ b/backend/src/modules/transactions/transactions.controller.ts @@ -11,9 +11,7 @@ import { import { Response } from 'express'; import { ApiBearerAuth, - ApiBody, ApiOperation, - ApiParam, ApiResponse, ApiTags, } from '@nestjs/swagger'; @@ -29,7 +27,7 @@ import { PageDto } from '../../common/dto/page.dto'; @ApiTags('Transactions') @Controller('transactions') @UseGuards(JwtAuthGuard) -@ApiBearerAuth('access-token') +@ApiBearerAuth() export class TransactionsController { constructor(private readonly transactionsService: TransactionsService) {} @@ -62,11 +60,7 @@ export class TransactionsController { description: 'Streams transactions as CSV for download with controlled memory usage while respecting query filters.', }) - @ApiResponse({ - status: 200, - description: 'CSV file stream', - schema: { type: 'string', format: 'binary' }, - }) + @ApiResponse({ status: 200, description: 'CSV file stream' }) @ApiResponse({ status: 401, description: 'Unauthorized - Invalid or missing JWT token', @@ -91,20 +85,6 @@ export class TransactionsController { } @Post(':id/tag') - @ApiOperation({ summary: 'Tag or categorize a transaction' }) - @ApiParam({ - name: 'id', - description: 'Transaction UUID', - type: 'string', - format: 'uuid', - }) - @ApiBody({ type: TagTransactionDto }) - @ApiResponse({ - status: 201, - description: 'Transaction tagged', - type: TransactionResponseDto, - }) - @ApiResponse({ status: 404, description: 'Transaction not found' }) async tagTransaction( @CurrentUser() user: { id: string }, @Param('id') id: string, @@ -114,20 +94,11 @@ export class TransactionsController { } @Get('categories') - @ApiOperation({ summary: 'List all categories used by the current user' }) - @ApiResponse({ - status: 200, - description: 'Array of category strings', - schema: { type: 'array', items: { type: 'string' } }, - }) async getCategories(@CurrentUser() user: { id: string }) { return this.transactionsService.listCategories(user.id); } @Post('tags/bulk') - @ApiOperation({ summary: 'Bulk-tag multiple transactions' }) - @ApiBody({ type: BulkTagDto }) - @ApiResponse({ status: 201, description: 'Transactions updated' }) async bulkTag(@CurrentUser() user: { id: string }, @Body() body: BulkTagDto) { return this.transactionsService.bulkTag(user.id, body); } diff --git a/backend/src/modules/user/dto/update-user.dto.ts b/backend/src/modules/user/dto/update-user.dto.ts index d7f5c90bd..6722f76c1 100644 --- a/backend/src/modules/user/dto/update-user.dto.ts +++ b/backend/src/modules/user/dto/update-user.dto.ts @@ -1,5 +1,4 @@ -import { IsOptional, IsString, MaxLength, IsDate } from 'class-validator'; -import { Type } from 'class-transformer'; +import { IsOptional, IsString, MaxLength } from 'class-validator'; export class UpdateUserDto { @IsOptional() @@ -11,11 +10,6 @@ export class UpdateUserDto { @IsString() @MaxLength(500) bio?: string; - - @IsOptional() - @IsDate() - @Type(() => Date) - lastLoginAt?: Date; } export class ApproveKycDto { diff --git a/backend/src/modules/user/entities/user.entity.ts b/backend/src/modules/user/entities/user.entity.ts index 081c734a6..3977fd534 100644 --- a/backend/src/modules/user/entities/user.entity.ts +++ b/backend/src/modules/user/entities/user.entity.ts @@ -74,16 +74,6 @@ export class User { @Column({ type: 'timestamp', nullable: true }) lastLoginAt: Date | null; - // Account lockout fields - @Column({ type: 'int', default: 0 }) - failedLoginAttempts: number; - - @Column({ type: 'timestamp', nullable: true }) - lockedUntil: Date | null; - - @Column({ type: 'boolean', default: false }) - isLocked: boolean; - @CreateDateColumn() createdAt: Date; diff --git a/backend/src/modules/user/user.controller.ts b/backend/src/modules/user/user.controller.ts index 33af3bd83..a9cf6e2cd 100644 --- a/backend/src/modules/user/user.controller.ts +++ b/backend/src/modules/user/user.controller.ts @@ -30,12 +30,8 @@ import { NetWorthDto } from './dto/net-worth.dto'; import { UserProfileResponseDto } from './dto/user-profile-response.dto'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; -import { ThrottlerGuard } from '@nestjs/throttler'; -import { FileValidator } from '@nestjs/common'; -// File upload configuration -const MAX_AVATAR_SIZE = 5 * 1024 * 1024; // 5MB -const MAX_KYC_DOC_SIZE = 10 * 1024 * 1024; // 10MB +import { FileValidator } from '@nestjs/common'; class ImageTypeValidator extends FileValidator { constructor() { @@ -181,17 +177,15 @@ export class UserController { } @Post('avatar') - @UseGuards(ThrottlerGuard) @UseInterceptors(FileInterceptor('file')) async uploadAvatar( @CurrentUser() user: { id: string }, @UploadedFile( new ParseFilePipe({ validators: [ - new MaxFileSizeValidator({ maxSize: MAX_AVATAR_SIZE }), + new MaxFileSizeValidator({ maxSize: 1024 * 1024 * 5 }), // 5MB new ImageTypeValidator(), ], - fileIsRequired: true, }), ) file: any, @@ -201,17 +195,15 @@ export class UserController { } @Post('me/kyc-docs') - @UseGuards(ThrottlerGuard) @UseInterceptors(FileInterceptor('document')) async uploadKycDocument( @CurrentUser() user: { id: string }, @UploadedFile( new ParseFilePipe({ validators: [ - new MaxFileSizeValidator({ maxSize: MAX_KYC_DOC_SIZE }), + new MaxFileSizeValidator({ maxSize: 1024 * 1024 * 10 }), // 10MB new KycDocumentValidator(), ], - fileIsRequired: true, }), ) file: any, diff --git a/backend/test/jest-e2e-setup.ts b/backend/test/jest-e2e-setup.ts index b2ad1b031..dc32443ac 100644 --- a/backend/test/jest-e2e-setup.ts +++ b/backend/test/jest-e2e-setup.ts @@ -46,19 +46,3 @@ process.env.REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; process.env.STELLAR_WEBHOOK_SECRET = process.env.STELLAR_WEBHOOK_SECRET || 'test_webhook_secret_long_enough_minimum_16_chars'; - -// Fallback URLs required by env validation -process.env.SOROBAN_RPC_FALLBACK_URLS = - process.env.SOROBAN_RPC_FALLBACK_URLS || - 'https://soroban-testnet.stellar.org'; -process.env.HORIZON_FALLBACK_URLS = - process.env.HORIZON_FALLBACK_URLS || 'https://horizon-testnet.stellar.org'; - -// APM configuration -process.env.APM_SAMPLING_RATE = process.env.APM_SAMPLING_RATE || '1.0'; -process.env.APM_ENABLED = process.env.APM_ENABLED || 'true'; - -// DB pool retry configuration (fast retries in tests) -process.env.DB_MAX_RETRIES = process.env.DB_MAX_RETRIES || '3'; -process.env.DB_RETRY_INITIAL_DELAY = process.env.DB_RETRY_INITIAL_DELAY || '100'; -process.env.DB_POOL_MONITOR_INTERVAL = process.env.DB_POOL_MONITOR_INTERVAL || '60000'; diff --git a/backend/test/jest-e2e.json b/backend/test/jest-e2e.json index 84d30fb8d..53e5b6b53 100644 --- a/backend/test/jest-e2e.json +++ b/backend/test/jest-e2e.json @@ -3,31 +3,9 @@ "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", + "setupFilesAfterEnv": ["./jest-e2e-setup.ts"], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "setupFilesAfterEnv": ["/jest-e2e-setup.ts"], - "collectCoverage": false, - "coverageDirectory": "../coverage/e2e", - "coverageReporters": ["text", "lcov", "html"], - "collectCoverageFrom": [ - "../src/**/*.ts", - "!../src/**/*.spec.ts", - "!../src/**/*.module.ts", - "!../src/main.ts", - "!../src/migrations/**", - "!../src/**/*.entity.ts" - ], - "coverageThreshold": { - "global": { - "branches": 60, - "functions": 70, - "lines": 75, - "statements": 75 - } - }, - "testTimeout": 30000, - "moduleNameMapper": { - "^@/(.*)$": "/../src/$1" - } + "setupFilesAfterEnv": ["/jest-e2e-setup.ts"] } diff --git a/contracts/src/config.rs b/contracts/src/config.rs index 37c60afdf..f702f9216 100644 --- a/contracts/src/config.rs +++ b/contracts/src/config.rs @@ -18,7 +18,6 @@ pub struct Config { pub withdrawal_fee_bps: u32, pub performance_fee_bps: u32, pub paused: bool, - pub upgrade_delay: u64, } // ========== Admin Verification ========== @@ -64,7 +63,6 @@ pub fn initialize_config( deposit_fee_bps: u32, withdrawal_fee_bps: u32, performance_fee_bps: u32, - upgrade_delay: u64, ) -> Result<(), SavingsError> { // Prevent re-initialization let already_init: bool = env @@ -100,9 +98,6 @@ pub fn initialize_config( env.storage() .instance() .set(&DataKey::PerformanceFeeBps, &performance_fee_bps); - env.storage() - .instance() - .set(&DataKey::NextTimelockId, &1u64); // Initialize timelock counter env.storage() .instance() .set(&DataKey::ConfigInitialized, &true); @@ -111,7 +106,7 @@ pub fn initialize_config( crate::treasury::initialize_treasury(env); env.events() - .publish((), crate::events::ProtocolEvent::CfgInit(performance_fee_bps)); + .publish((symbol_short!("cfg_init"),), performance_fee_bps); Ok(()) } @@ -171,7 +166,6 @@ pub fn get_config(env: &Env) -> Result { withdrawal_fee_bps, performance_fee_bps, paused, - upgrade_delay: 86400 * 2, // 2 days default }) } @@ -192,7 +186,7 @@ pub fn set_treasury(env: &Env, admin: Address, new_treasury: Address) -> Result< .set(&DataKey::TreasuryAddress, &new_treasury); env.events() - .publish((), crate::events::ProtocolEvent::SetTreasury(new_treasury)); + .publish((symbol_short!("set_trs"),), new_treasury); Ok(()) } @@ -231,7 +225,7 @@ pub fn set_fees( .set(&DataKey::PerformanceFeeBps, &performance_fee); env.events() - .publish((), crate::events::ProtocolEvent::SetFees(performance_fee)); + .publish((symbol_short!("set_fee"),), performance_fee); Ok(()) } @@ -249,8 +243,7 @@ pub fn pause_contract(env: &Env, admin: Address) -> Result<(), SavingsError> { env.storage().persistent().set(&DataKey::Paused, &true); - env.events() - .publish((), crate::events::ProtocolEvent::CfgPause(admin)); + env.events().publish((symbol_short!("pause"),), admin); Ok(()) } @@ -268,8 +261,7 @@ pub fn unpause_contract(env: &Env, admin: Address) -> Result<(), SavingsError> { env.storage().persistent().set(&DataKey::Paused, &false); - env.events() - .publish((), crate::events::ProtocolEvent::CfgUnpause(admin)); + env.events().publish((symbol_short!("unpause"),), admin); Ok(()) } diff --git a/contracts/src/flexi.rs b/contracts/src/flexi.rs index 5f9d2d9db..8dd39419b 100644 --- a/contracts/src/flexi.rs +++ b/contracts/src/flexi.rs @@ -78,10 +78,8 @@ pub fn flexi_deposit(env: Env, user: Address, amount: i128) -> Result<(), Saving .checked_add(fee_amount) .ok_or(SavingsError::Overflow)?; env.storage().persistent().set(&fee_key, &new_fee_balance); - env.events().publish( - (), - crate::events::FlexiEvent::Fee(fee_recipient, fee_amount, soroban_sdk::Symbol::new(&env, "deposit")), - ); + env.events() + .publish((symbol_short!("dep_fee"), fee_recipient), fee_amount); } // Record fee in treasury struct crate::treasury::record_fee(&env, fee_amount, soroban_sdk::Symbol::new(&env, "deposit")); @@ -165,10 +163,8 @@ pub fn flexi_withdraw(env: Env, user: Address, amount: i128) -> Result<(), Savin .checked_add(fee_amount) .ok_or(SavingsError::Overflow)?; env.storage().persistent().set(&fee_key, &new_fee_balance); - env.events().publish( - (), - crate::events::FlexiEvent::Fee(fee_recipient, fee_amount, soroban_sdk::Symbol::new(&env, "withdraw")), - ); + env.events() + .publish((symbol_short!("wth_fee"), fee_recipient), fee_amount); } // Record fee in treasury struct crate::treasury::record_fee(&env, fee_amount, soroban_sdk::Symbol::new(&env, "withdraw")); diff --git a/contracts/src/goal.rs b/contracts/src/goal.rs index f6d39f2df..5ae272286 100644 --- a/contracts/src/goal.rs +++ b/contracts/src/goal.rs @@ -291,8 +291,8 @@ pub fn withdraw_completed_goal_save( .ok_or(SavingsError::Overflow)?; env.storage().persistent().set(&fee_key, &new_fee_balance); env.events().publish( - (), - crate::events::GoalEvent::Fee(fee_recipient, goal_id, fee_amount, soroban_sdk::Symbol::new(env, "withdraw")), + (symbol_short!("gwth_fee"), fee_recipient, goal_id), + fee_amount, ); } // Record fee in treasury struct @@ -385,15 +385,15 @@ pub fn break_goal_save(env: &Env, user: Address, goal_id: u64) -> Result Result<(), Sa // Emit event for joining group env.events() - .publish((), crate::events::ProtocolEvent::GroupJoin(user, group_id)); + .publish((soroban_sdk::symbol_short!("grp_join"), user), group_id); Ok(()) } @@ -476,8 +476,8 @@ pub fn contribute_to_group_save( // Emit event for contribution env.events().publish( - (), - crate::events::ProtocolEvent::GroupContribute(user, group_id, amount), + (soroban_sdk::symbol_short!("grp_cont"), user, group_id), + amount, ); Ok(()) @@ -674,8 +674,8 @@ pub fn break_group_save(env: &Env, user: Address, group_id: u64) -> Result<(), S // Emit event for leaving group env.events().publish( - (), - crate::events::ProtocolEvent::GroupBreak(user, group_id), + (soroban_sdk::symbol_short!("grp_leave"), user, group_id), + user_contribution, ); Ok(()) diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 480ce8b28..0239739be 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -23,17 +23,10 @@ pub mod strategy; pub mod token; pub mod treasury; mod ttl; -mod events; mod upgrade; -mod timelock; mod users; mod security; -mod multisig; -mod storage_optimization; -mod gas_optimization; -#[cfg(test)] -mod fuzz_tests; mod rates; mod views; @@ -185,7 +178,7 @@ impl NesteraContract { ttl::extend_instance_ttl(&env); env.events() - .publish((), events::ProtocolEvent::Init(admin_public_key)); + .publish((symbol_short!("init"),), admin_public_key); } pub fn verify_signature(env: Env, payload: MintPayload, signature: BytesN<64>) -> bool { @@ -270,8 +263,8 @@ impl NesteraContract { // 3. INTERACTIONS (Events) crate::security::release_reentrancy_guard(&env); env.events().publish( - (), - events::ProtocolEvent::CreatePlan(user, plan_id, initial_deposit), + (Symbol::new(&env, "create_plan"), user, plan_id), + initial_deposit, ); Ok(plan_id) @@ -479,7 +472,7 @@ impl NesteraContract { } env.storage().instance().set(&DataKey::Admin, &new_admin); env.events() - .publish((), events::ProtocolEvent::SetAdmin(new_admin)); + .publish((symbol_short!("set_admin"),), new_admin); Ok(()) } @@ -513,7 +506,7 @@ impl NesteraContract { env.storage() .instance() .set(&DataKey::EarlyBreakFeeBps, &bps); - env.events().publish((), events::ProtocolEvent::SetEarlyBreakFee(bps)); + env.events().publish((symbol_short!("set_brk"),), bps); Ok(()) } @@ -523,7 +516,7 @@ impl NesteraContract { env.storage() .instance() .set(&DataKey::FeeRecipient, &recipient); - env.events().publish((), events::ProtocolEvent::SetFeeRecipient(recipient)); + env.events().publish((symbol_short!("set_fee"),), recipient); Ok(()) } @@ -533,7 +526,7 @@ impl NesteraContract { env.storage().persistent().set(&DataKey::Paused, &true); ttl::extend_config_ttl(&env, &DataKey::Paused); - env.events().publish((), events::ProtocolEvent::Pause(caller)); + env.events().publish((symbol_short!("pause"), caller), ()); Ok(()) } @@ -543,7 +536,7 @@ impl NesteraContract { env.storage().persistent().set(&DataKey::Paused, &false); ttl::extend_config_ttl(&env, &DataKey::Paused); - env.events().publish((), events::ProtocolEvent::Unpause(caller)); + env.events().publish((symbol_short!("unpause"), caller), ()); Ok(()) } @@ -702,8 +695,8 @@ impl NesteraContract { // 5. Emit event env.events().publish( - (), - events::ProtocolEvent::EmergencyWithdraw(user, plan_id, withdrawn_amount), + (Symbol::new(&env, "emergency_withdraw"), user, plan_id), + withdrawn_amount, ); Ok(withdrawn_amount) @@ -1132,7 +1125,6 @@ impl NesteraContract { deposit_fee_bps: u32, withdrawal_fee_bps: u32, performance_fee_bps: u32, - upgrade_delay: u64, ) -> Result<(), SavingsError> { config::initialize_config( &env, @@ -1141,7 +1133,6 @@ impl NesteraContract { deposit_fee_bps, withdrawal_fee_bps, performance_fee_bps, - upgrade_delay, ) } @@ -1490,6 +1481,7 @@ impl NesteraContract { res } + /// Returns the performance metrics for a give strategy. pub fn get_strategy_performance(_env: &Env) -> StrategyPerformance { StrategyPerformance { total_deposited: 0, @@ -1498,32 +1490,6 @@ impl NesteraContract { apy_estimate_bps: 0, } } - - // ========== Security & Upgrade Logic ========== - - pub fn schedule_upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) -> Result<(), SavingsError> { - upgrade::schedule_upgrade(&env, admin.clone(), new_wasm_hash.clone())?; - env.events().publish((), events::ProtocolEvent::UpgradeScheduled(admin, new_wasm_hash)); - Ok(()) - } - - pub fn upgrade_contract(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), SavingsError> { - upgrade::upgrade(&env, new_wasm_hash.clone())?; - env.events().publish((), events::ProtocolEvent::ContractUpgraded(new_wasm_hash)); - Ok(()) - } - - pub fn timelock_queue(env: Env, admin: Address, action: Symbol, payload: Bytes, delay: u64) -> Result { - let proposal_id = timelock::queue_action(&env, admin.clone(), action.clone(), payload, delay)?; - env.events().publish((), events::ProtocolEvent::TimelockQueued(proposal_id, admin, action)); - Ok(proposal_id) - } - - pub fn timelock_execute(env: Env, admin: Address, proposal_id: u64) -> Result { - let proposal = timelock::execute_action(&env, admin.clone(), proposal_id)?; - env.events().publish((), events::ProtocolEvent::TimelockExecuted(proposal_id, admin, proposal.action.clone())); - Ok(proposal) - } } #[cfg(test)] diff --git a/contracts/src/lock.rs b/contracts/src/lock.rs index a7f663908..dada212d9 100644 --- a/contracts/src/lock.rs +++ b/contracts/src/lock.rs @@ -72,11 +72,6 @@ pub fn create_lock_save( ttl::extend_user_ttl(env, &user); ttl::extend_user_plan_list_ttl(env, &DataKey::UserLockSaves(user.clone())); - env.events().publish( - (), - crate::events::ProtocolEvent::LockCreated(user, amount, duration, lock_id), - ); - Ok(lock_id) } @@ -116,7 +111,8 @@ pub fn withdraw_lock_save(env: &Env, user: Address, lock_id: u64) -> Result Result Result Result .instance() .set(&DataKey::TokenMetadata, &metadata); - env.events().publish((), crate::events::ProtocolEvent::Mint(to, amount)); + // Emit TokenMinted event + let event = TokenMinted { + to: to.clone(), + amount, + new_total_supply: metadata.total_supply, + }; + env.events() + .publish((symbol_short!("token"), symbol_short!("mint"), to), event); Ok(metadata.total_supply) } @@ -143,7 +167,14 @@ pub fn burn(env: &Env, from: Address, amount: i128) -> Result) -> Result<(), crate::errors::SavingsError> { - let admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(crate::errors::SavingsError::Unauthorized)?; - admin.require_auth(); +#[contracttype] +pub enum UpgradeDataKey { + ContractVersion, +} - // Check for time-lock if implemented - if let Some(scheduled_hash) = env.storage().instance().get::>(&DataKey::UpgradeScheduled) { - if scheduled_hash != new_wasm_hash { - return Err(crate::errors::SavingsError::Unauthorized); - } - - let scheduled_at: u64 = env.storage().instance().get(&DataKey::UpgradeScheduledAt).unwrap_or(0); - let config = crate::config::get_config(env)?; - if env.ledger().timestamp() < scheduled_at + config.upgrade_delay { - return Err(crate::errors::SavingsError::TooEarly); - } - } else { - // If no time-lock, we might want to enforce one or allow admin if it's not enabled - } +const CONTRACT_VERSION: u32 = 1; - env.deployer().update_current_contract_wasm(new_wasm_hash); - - // Clear scheduled upgrade - env.storage().instance().remove(&DataKey::UpgradeScheduled); - env.storage().instance().remove(&DataKey::UpgradeScheduledAt); - - Ok(()) +pub fn get_version(env: &Env) -> u32 { + env.storage() + .instance() + .get(&UpgradeDataKey::ContractVersion) + .unwrap_or(0) } -pub fn schedule_upgrade(env: &Env, admin: Address, new_wasm_hash: soroban_sdk::BytesN<32>) -> Result<(), crate::errors::SavingsError> { +pub fn set_version(env: &Env, version: u32) { + env.storage() + .instance() + .set(&UpgradeDataKey::ContractVersion, &version); +} + +pub fn upgrade_contract(env: &Env, admin: Address, new_wasm_hash: BytesN<32>) { + // 1. Verify Authorization admin.require_auth(); - let stored_admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(crate::errors::SavingsError::Unauthorized)?; - if admin != stored_admin { - return Err(crate::errors::SavingsError::Unauthorized); + + // 2. Perform Version Validation (Migration Safety) + let current_version = get_version(env); + let new_version = CONTRACT_VERSION; // This would typically come from the new WASM logic + + if new_version <= current_version { + // You could define a custom error for "InvalidVersion" + panic!("New version must be greater than current version"); } - env.storage().instance().set(&DataKey::UpgradeScheduled, &new_wasm_hash); - env.storage().instance().set(&DataKey::UpgradeScheduledAt, &env.ledger().timestamp()); - - Ok(()) + // 3. Update the WASM + env.deployer().update_current_contract_wasm(new_wasm_hash); + + // 4. Run Migration Logic if necessary + migrate(env, current_version); + + // 5. Update stored version + set_version(env, new_version); +} + +fn migrate(_env: &Env, _from_version: u32) { + // Placeholder for future state migrations + // Example: if from_version == 1 { ... upgrade storage structures ... } } diff --git a/eslint.config.js b/eslint.config.js index 02d37fcb8..8a2055cc1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,17 +1,18 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format +import storybook from "eslint-plugin-storybook"; + import tsParser from "@typescript-eslint/parser"; -export default [ - { - files: ["**/*.{js,jsx,ts,tsx}"], - ignores: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"], - languageOptions: { - parser: tsParser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - ecmaFeatures: { jsx: true }, - }, +export default [{ + files: ["**/*.{js,jsx,ts,tsx}"], + ignores: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, }, - rules: {}, }, -]; + rules: {}, +}, ...storybook.configs["flat/recommended"]]; diff --git a/frontend/.codex-next-dev.err.log b/frontend/.codex-next-dev.err.log new file mode 100644 index 000000000..a9e6d8a55 --- /dev/null +++ b/frontend/.codex-next-dev.err.log @@ -0,0 +1,171 @@ +⚠ Warning: Next.js inferred your workspace root, but it may not be correct. + We detected multiple lockfiles and selected the directory of C:\Users\Tumi\Desktop\nike\Nestera\pnpm-workspace.yaml as the root directory. + To silence this warning, set `turbopack.root` in your Next.js config, or consider removing one of the lockfiles if it's not needed. + See https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack#root-directory for more information. + Detected additional lockfiles: + * C:\Users\Tumi\Desktop\nike\Nestera\frontend\pnpm-workspace.yaml + +⚠ The "middleware" file convention is deprecated. Please use "proxy" instead. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy +Error: ENVIRONMENT_FALLBACK: There is no `timeZone` configured, this can lead to markup mismatches caused by environment differences. Consider adding a global default: https://next-intl.dev/docs/configuration#time-zone + at Navbar (app\components\Navbar.tsx:19:28) + 17 | const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + 18 | const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); +> 19 | const t = useTranslations("Navbar"); + | ^ + 20 | const locale = useLocale(); + 21 | + 22 | const navLinks: NavLink[] = [ { + code: 'ENVIRONMENT_FALLBACK', + originalMessage: 'There is no `timeZone` configured, this can lead to markup mismatches caused by environment differences. Consider adding a global default: https://next-intl.dev/docs/configuration#time-zone' +} +Error: MISSING_MESSAGE: Could not resolve `Support.Support.title` in messages for locale `es`. + at SupportPage (app\support\page.tsx:53:12) + 51 |
+ 52 |

+> 53 | {t("Support.title")} + | ^ + 54 |

+ 55 | + 56 |
+ 62 |