From 976901279505257ebe6e61d721255bfa0e11e210 Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 1 Dec 2025 01:57:40 -0800 Subject: [PATCH] feat(reward-service): Implement complete reward service with DDD architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Core Features - 6 types of reward calculation (Share Right 500U, Province Team 20U, Province Area 15U+1% hashpower, City Team 40U, City Area 35U+2% hashpower, Community Right 80U) - 24-hour countdown mechanism for pending rewards - Reward settlement with multi-currency support (BNB/OG/USDT/DST) - Automatic reward expiration with scheduled tasks ## Domain Layer - RewardLedgerEntry aggregate root with state machine (PENDING -> SETTLEABLE -> SETTLED, PENDING -> EXPIRED) - RewardSummary aggregate for user reward overview - Value objects: Money, Hashpower, RewardSource, RewardStatus, RightType - Domain events: RewardCreated, RewardClaimed, RewardSettled, RewardExpired - Domain services: RewardCalculationService, RewardExpirationService ## Application Layer - RewardApplicationService for orchestrating business workflows - RewardExpirationScheduler for automatic expiration processing ## Infrastructure Layer - PostgreSQL persistence with Prisma ORM - Redis caching integration - Kafka event publishing/consuming - External service clients (Referral, Authorization, Wallet) ## API Layer - REST endpoints: /health, /rewards/summary, /rewards/details, /rewards/pending, /rewards/settle - JWT authentication with Passport.js - Swagger/OpenAPI documentation ## Testing (77 tests, 100% pass rate) - 43 unit tests for domain logic (Money, Hashpower, aggregates) - 20 integration tests for application services - 14 E2E tests for API endpoints - Docker Compose test infrastructure (PostgreSQL, Redis, Kafka) ## Documentation - docs/ARCHITECTURE.md - DDD architecture overview - docs/API.md - REST API documentation - docs/DEVELOPMENT.md - Developer guide - docs/TESTING.md - Testing guide - docs/DEPLOYMENT.md - Deployment guide ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../services/reward-service/.env.development | 34 + backend/services/reward-service/.env.example | 34 + backend/services/reward-service/.env.test | 25 + backend/services/reward-service/.gitignore | 40 + backend/services/reward-service/.prettierrc | 4 + backend/services/reward-service/Dockerfile | 47 + .../services/reward-service/Dockerfile.test | 18 + backend/services/reward-service/Makefile | 93 + backend/services/reward-service/README.md | 98 + .../reward-service/docker-compose.test.yml | 74 + backend/services/reward-service/docs/API.md | 426 + .../reward-service/docs/ARCHITECTURE.md | 511 + .../reward-service/docs/DEPLOYMENT.md | 679 + .../reward-service/docs/DEVELOPMENT.md | 544 + .../services/reward-service/docs/TESTING.md | 808 ++ .../services/reward-service/eslint.config.mjs | 35 + backend/services/reward-service/nest-cli.json | 8 + .../services/reward-service/package-lock.json | 11412 ++++++++++++++++ backend/services/reward-service/package.json | 94 + .../services/reward-service/prisma.config.ts | 14 + .../reward-service/prisma/schema.prisma | 175 + .../services/reward-service/prisma/seed.ts | 70 + .../reward-service/src/api/api.module.ts | 29 + .../src/api/controllers/health.controller.ts | 17 + .../src/api/controllers/reward.controller.ts | 61 + .../api/controllers/settlement.controller.ts | 30 + .../src/api/dto/request/settle-rewards.dto.ts | 20 + .../src/api/dto/response/reward-entry.dto.ts | 53 + .../api/dto/response/reward-summary.dto.ts | 33 + .../api/dto/response/settlement-result.dto.ts | 21 + .../services/reward-service/src/app.module.ts | 16 + .../src/application/application.module.ts | 20 + .../schedulers/reward-expiration.scheduler.ts | 27 + .../services/reward-application.service.ts | 340 + .../reward-service/src/config/app.config.ts | 7 + .../reward-service/src/config/index.ts | 1 + .../aggregates/reward-ledger-entry/index.ts | 1 + .../reward-ledger-entry.aggregate.ts | 282 + .../reward-ledger-entry.spec.ts | 211 + .../domain/aggregates/reward-summary/index.ts | 1 + .../reward-summary.aggregate.ts | 168 + .../reward-summary/reward-summary.spec.ts | 149 + .../src/domain/domain.module.ts | 15 + .../src/domain/events/domain-event.base.ts | 18 + .../reward-service/src/domain/events/index.ts | 5 + .../src/domain/events/reward-claimed.event.ts | 30 + .../src/domain/events/reward-created.event.ts | 37 + .../src/domain/events/reward-expired.event.ts | 31 + .../src/domain/events/reward-settled.event.ts | 32 + .../src/domain/repositories/index.ts | 2 + ...eward-ledger-entry.repository.interface.ts | 26 + .../reward-summary.repository.interface.ts | 11 + .../src/domain/services/index.ts | 2 + .../services/reward-calculation.service.ts | 347 + .../services/reward-expiration.service.ts | 45 + .../domain/value-objects/hashpower.spec.ts | 76 + .../src/domain/value-objects/hashpower.vo.ts | 40 + .../src/domain/value-objects/index.ts | 5 + .../src/domain/value-objects/money.spec.ts | 86 + .../src/domain/value-objects/money.vo.ts | 48 + .../domain/value-objects/reward-source.vo.ts | 25 + .../value-objects/reward-status.enum.ts | 6 + .../domain/value-objects/right-type.enum.ts | 18 + .../authorization-service.client.ts | 70 + .../referral-service.client.ts | 38 + .../wallet-service/wallet-service.client.ts | 80 + .../infrastructure/infrastructure.module.ts | 48 + .../kafka/event-consumer.controller.ts | 48 + .../kafka/event-publisher.service.ts | 58 + .../src/infrastructure/kafka/kafka.module.ts | 31 + .../mappers/reward-ledger-entry.mapper.ts | 47 + .../mappers/reward-summary.mapper.ts | 40 + .../persistence/prisma/prisma.service.ts | 13 + .../reward-ledger-entry.repository.impl.ts | 155 + .../reward-summary.repository.impl.ts | 91 + .../src/infrastructure/redis/redis.module.ts | 19 + .../src/infrastructure/redis/redis.service.ts | 64 + backend/services/reward-service/src/main.ts | 43 + .../src/shared/guards/jwt-auth.guard.ts | 16 + .../src/shared/strategies/jwt.strategy.ts | 23 + .../reward-service/test/app.e2e-spec.ts | 318 + .../reward-application.service.spec.ts | 354 + .../reward-calculation.service.spec.ts | 233 + .../reward-service/test/jest-e2e.json | 9 + .../reward-service/tsconfig.build.json | 4 + backend/services/reward-service/tsconfig.json | 25 + 86 files changed, 19462 insertions(+) create mode 100644 backend/services/reward-service/.env.development create mode 100644 backend/services/reward-service/.env.example create mode 100644 backend/services/reward-service/.env.test create mode 100644 backend/services/reward-service/.gitignore create mode 100644 backend/services/reward-service/.prettierrc create mode 100644 backend/services/reward-service/Dockerfile.test create mode 100644 backend/services/reward-service/Makefile create mode 100644 backend/services/reward-service/README.md create mode 100644 backend/services/reward-service/docker-compose.test.yml create mode 100644 backend/services/reward-service/docs/API.md create mode 100644 backend/services/reward-service/docs/ARCHITECTURE.md create mode 100644 backend/services/reward-service/docs/DEPLOYMENT.md create mode 100644 backend/services/reward-service/docs/DEVELOPMENT.md create mode 100644 backend/services/reward-service/docs/TESTING.md create mode 100644 backend/services/reward-service/eslint.config.mjs create mode 100644 backend/services/reward-service/nest-cli.json create mode 100644 backend/services/reward-service/package-lock.json create mode 100644 backend/services/reward-service/prisma.config.ts create mode 100644 backend/services/reward-service/prisma/schema.prisma create mode 100644 backend/services/reward-service/prisma/seed.ts create mode 100644 backend/services/reward-service/src/api/api.module.ts create mode 100644 backend/services/reward-service/src/api/controllers/health.controller.ts create mode 100644 backend/services/reward-service/src/api/controllers/reward.controller.ts create mode 100644 backend/services/reward-service/src/api/controllers/settlement.controller.ts create mode 100644 backend/services/reward-service/src/api/dto/request/settle-rewards.dto.ts create mode 100644 backend/services/reward-service/src/api/dto/response/reward-entry.dto.ts create mode 100644 backend/services/reward-service/src/api/dto/response/reward-summary.dto.ts create mode 100644 backend/services/reward-service/src/api/dto/response/settlement-result.dto.ts create mode 100644 backend/services/reward-service/src/app.module.ts create mode 100644 backend/services/reward-service/src/application/application.module.ts create mode 100644 backend/services/reward-service/src/application/schedulers/reward-expiration.scheduler.ts create mode 100644 backend/services/reward-service/src/application/services/reward-application.service.ts create mode 100644 backend/services/reward-service/src/config/app.config.ts create mode 100644 backend/services/reward-service/src/config/index.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/index.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.spec.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-summary/index.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.aggregate.ts create mode 100644 backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.spec.ts create mode 100644 backend/services/reward-service/src/domain/domain.module.ts create mode 100644 backend/services/reward-service/src/domain/events/domain-event.base.ts create mode 100644 backend/services/reward-service/src/domain/events/index.ts create mode 100644 backend/services/reward-service/src/domain/events/reward-claimed.event.ts create mode 100644 backend/services/reward-service/src/domain/events/reward-created.event.ts create mode 100644 backend/services/reward-service/src/domain/events/reward-expired.event.ts create mode 100644 backend/services/reward-service/src/domain/events/reward-settled.event.ts create mode 100644 backend/services/reward-service/src/domain/repositories/index.ts create mode 100644 backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts create mode 100644 backend/services/reward-service/src/domain/repositories/reward-summary.repository.interface.ts create mode 100644 backend/services/reward-service/src/domain/services/index.ts create mode 100644 backend/services/reward-service/src/domain/services/reward-calculation.service.ts create mode 100644 backend/services/reward-service/src/domain/services/reward-expiration.service.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/hashpower.spec.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/hashpower.vo.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/index.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/money.spec.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/money.vo.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/reward-status.enum.ts create mode 100644 backend/services/reward-service/src/domain/value-objects/right-type.enum.ts create mode 100644 backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts create mode 100644 backend/services/reward-service/src/infrastructure/external/referral-service/referral-service.client.ts create mode 100644 backend/services/reward-service/src/infrastructure/external/wallet-service/wallet-service.client.ts create mode 100644 backend/services/reward-service/src/infrastructure/infrastructure.module.ts create mode 100644 backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts create mode 100644 backend/services/reward-service/src/infrastructure/kafka/event-publisher.service.ts create mode 100644 backend/services/reward-service/src/infrastructure/kafka/kafka.module.ts create mode 100644 backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts create mode 100644 backend/services/reward-service/src/infrastructure/persistence/mappers/reward-summary.mapper.ts create mode 100644 backend/services/reward-service/src/infrastructure/persistence/prisma/prisma.service.ts create mode 100644 backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts create mode 100644 backend/services/reward-service/src/infrastructure/persistence/repositories/reward-summary.repository.impl.ts create mode 100644 backend/services/reward-service/src/infrastructure/redis/redis.module.ts create mode 100644 backend/services/reward-service/src/infrastructure/redis/redis.service.ts create mode 100644 backend/services/reward-service/src/main.ts create mode 100644 backend/services/reward-service/src/shared/guards/jwt-auth.guard.ts create mode 100644 backend/services/reward-service/src/shared/strategies/jwt.strategy.ts create mode 100644 backend/services/reward-service/test/app.e2e-spec.ts create mode 100644 backend/services/reward-service/test/integration/reward-application.service.spec.ts create mode 100644 backend/services/reward-service/test/integration/reward-calculation.service.spec.ts create mode 100644 backend/services/reward-service/test/jest-e2e.json create mode 100644 backend/services/reward-service/tsconfig.build.json diff --git a/backend/services/reward-service/.env.development b/backend/services/reward-service/.env.development new file mode 100644 index 00000000..335504dc --- /dev/null +++ b/backend/services/reward-service/.env.development @@ -0,0 +1,34 @@ +# ๅบ”็”จ้…็ฝฎ +NODE_ENV=development +PORT=3005 +APP_NAME=reward-service + +# ๆ•ฐๆฎๅบ“ +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_reward?schema=public" + +# JWT (ไธŽ identity-service ๅ…ฑไบซๅฏ†้’ฅ) +JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_ACCESS_EXPIRES_IN=2h + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# Kafka +KAFKA_BROKERS=localhost:9092 +KAFKA_GROUP_ID=reward-service-group +KAFKA_CLIENT_ID=reward-service + +# ๅค–้ƒจๆœๅŠก +IDENTITY_SERVICE_URL=http://localhost:3001 +REFERRAL_SERVICE_URL=http://localhost:3004 +AUTHORIZATION_SERVICE_URL=http://localhost:3006 +WALLET_SERVICE_URL=http://localhost:3002 +PLANTING_SERVICE_URL=http://localhost:3003 + +# ๅฅ–ๅŠฑ่ฟ‡ๆœŸๆฃ€ๆŸฅ้—ด้š”๏ผˆๆฏซ็ง’๏ผ‰ +REWARD_EXPIRATION_CHECK_INTERVAL=3600000 + +# ๆ€ป้ƒจ็คพๅŒบ่ดฆๆˆทID +HEADQUARTERS_COMMUNITY_USER_ID=1 diff --git a/backend/services/reward-service/.env.example b/backend/services/reward-service/.env.example new file mode 100644 index 00000000..335504dc --- /dev/null +++ b/backend/services/reward-service/.env.example @@ -0,0 +1,34 @@ +# ๅบ”็”จ้…็ฝฎ +NODE_ENV=development +PORT=3005 +APP_NAME=reward-service + +# ๆ•ฐๆฎๅบ“ +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_reward?schema=public" + +# JWT (ไธŽ identity-service ๅ…ฑไบซๅฏ†้’ฅ) +JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_ACCESS_EXPIRES_IN=2h + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# Kafka +KAFKA_BROKERS=localhost:9092 +KAFKA_GROUP_ID=reward-service-group +KAFKA_CLIENT_ID=reward-service + +# ๅค–้ƒจๆœๅŠก +IDENTITY_SERVICE_URL=http://localhost:3001 +REFERRAL_SERVICE_URL=http://localhost:3004 +AUTHORIZATION_SERVICE_URL=http://localhost:3006 +WALLET_SERVICE_URL=http://localhost:3002 +PLANTING_SERVICE_URL=http://localhost:3003 + +# ๅฅ–ๅŠฑ่ฟ‡ๆœŸๆฃ€ๆŸฅ้—ด้š”๏ผˆๆฏซ็ง’๏ผ‰ +REWARD_EXPIRATION_CHECK_INTERVAL=3600000 + +# ๆ€ป้ƒจ็คพๅŒบ่ดฆๆˆทID +HEADQUARTERS_COMMUNITY_USER_ID=1 diff --git a/backend/services/reward-service/.env.test b/backend/services/reward-service/.env.test new file mode 100644 index 00000000..107b79ac --- /dev/null +++ b/backend/services/reward-service/.env.test @@ -0,0 +1,25 @@ +# Test Environment +NODE_ENV=test +PORT=3005 + +# Database (for integration tests with Docker) +DATABASE_URL=postgresql://test:test@localhost:5433/reward_test + +# JWT +JWT_SECRET=test-secret-key-for-testing +JWT_ACCESS_EXPIRES_IN=1h + +# Redis (for integration tests with Docker) +REDIS_HOST=localhost +REDIS_PORT=6380 +REDIS_PASSWORD= + +# Kafka (for integration tests with Docker) +KAFKA_BROKERS=localhost:9093 +KAFKA_CLIENT_ID=reward-service-test +KAFKA_GROUP_ID=reward-service-test-group + +# External Services (mocked in tests) +REFERRAL_SERVICE_URL=http://localhost:3001 +AUTHORIZATION_SERVICE_URL=http://localhost:3002 +WALLET_SERVICE_URL=http://localhost:3003 diff --git a/backend/services/reward-service/.gitignore b/backend/services/reward-service/.gitignore new file mode 100644 index 00000000..87438789 --- /dev/null +++ b/backend/services/reward-service/.gitignore @@ -0,0 +1,40 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +build/ + +# Environment variables +.env +.env.local +.env.*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* + +# Testing +coverage/ + +# Prisma +/generated/prisma + +# Temporary files +*.tmp +*.temp +.cache/ + +# Claude Code +.claude/ diff --git a/backend/services/reward-service/.prettierrc b/backend/services/reward-service/.prettierrc new file mode 100644 index 00000000..a20502b7 --- /dev/null +++ b/backend/services/reward-service/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/backend/services/reward-service/Dockerfile b/backend/services/reward-service/Dockerfile index e69de29b..88dad92b 100644 --- a/backend/services/reward-service/Dockerfile +++ b/backend/services/reward-service/Dockerfile @@ -0,0 +1,47 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY prisma ./prisma/ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Generate Prisma client +RUN npx prisma generate + +# Build the application +RUN npm run build + +# Production stage +FROM node:20-alpine AS production + +WORKDIR /app + +# Copy package files and install production dependencies +COPY package*.json ./ +RUN npm ci --only=production + +# Copy Prisma files +COPY prisma ./prisma/ + +# Generate Prisma client for production +RUN npx prisma generate + +# Copy built application from builder stage +COPY --from=builder /app/dist ./dist + +# Set environment +ENV NODE_ENV=production +ENV PORT=3005 + +EXPOSE 3005 + +# Run the application +CMD ["node", "dist/main"] diff --git a/backend/services/reward-service/Dockerfile.test b/backend/services/reward-service/Dockerfile.test new file mode 100644 index 00000000..eacb4584 --- /dev/null +++ b/backend/services/reward-service/Dockerfile.test @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci + +# Copy Prisma files and generate client +COPY prisma ./prisma/ +COPY prisma.config.ts ./ +RUN npx prisma generate + +# Copy source code and tests +COPY . . + +# Run tests +CMD ["npm", "test"] diff --git a/backend/services/reward-service/Makefile b/backend/services/reward-service/Makefile new file mode 100644 index 00000000..ce85d169 --- /dev/null +++ b/backend/services/reward-service/Makefile @@ -0,0 +1,93 @@ +.PHONY: install build test test-unit test-integration test-e2e test-cov test-docker-all clean docker-up docker-down lint format + +# ============================================ +# ๅŸบ็ก€ๅ‘ฝไปค +# ============================================ + +install: + npm install + +build: + npm run build + +lint: + npm run lint + +format: + npm run format + +clean: + rm -rf dist coverage node_modules + +# ============================================ +# ๆต‹่ฏ•ๅ‘ฝไปค +# ============================================ + +# ๅ•ๅ…ƒๆต‹่ฏ• - ๆต‹่ฏ•้ข†ๅŸŸ้€ป่พ‘ๅ’Œๅ€ผๅฏน่ฑก +test-unit: + npm test -- --testPathPatterns='src/.*\.spec\.ts$$' --verbose + +# ้›†ๆˆๆต‹่ฏ• - ๆต‹่ฏ•ๆœๅŠกๅฑ‚ๅ’Œไป“ๅ‚จ +test-integration: + npm test -- --testPathPatterns='test/integration/.*\.spec\.ts$$' --verbose + +# ็ซฏๅˆฐ็ซฏๆต‹่ฏ• - ๆต‹่ฏ•ๅฎŒๆ•ดAPIๆต็จ‹ +test-e2e: + npm run test:e2e -- --verbose + +# ่ฆ†็›–็އๆต‹่ฏ• +test-cov: + npm run test:cov + +# ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• +test-all: test-unit test-integration test-e2e + +# ============================================ +# Docker ๆต‹่ฏ•ๅ‘ฝไปค +# ============================================ + +# ๅฏๅŠจๆต‹่ฏ•ไพ่ต–ๆœๅŠก +docker-up: + docker-compose -f docker-compose.test.yml up -d + +# ๅ…ณ้—ญๆต‹่ฏ•ไพ่ต–ๆœๅŠก +docker-down: + docker-compose -f docker-compose.test.yml down -v + +# ๅœจDockerไธญ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• +test-docker-all: + docker-compose -f docker-compose.test.yml up -d + sleep 5 + npm test -- --testPathPatterns='src/.*\.spec\.ts$$' --verbose || true + npm test -- --testPathPatterns='test/integration/.*\.spec\.ts$$' --verbose || true + npm run test:e2e -- --verbose || true + docker-compose -f docker-compose.test.yml down -v + +# ๆž„ๅปบๆต‹่ฏ•Docker้•œๅƒ +docker-build-test: + docker build -t reward-service-test:latest -f Dockerfile.test . + +# ๅœจDockerๅฎนๅ™จไธญ่ฟ่กŒๆต‹่ฏ• +docker-run-test: + docker run --rm \ + --network reward-test-network \ + -e DATABASE_URL="postgresql://test:test@postgres:5432/reward_test" \ + -e REDIS_HOST=redis \ + -e KAFKA_BROKERS=kafka:9092 \ + reward-service-test:latest + +# ============================================ +# ๆ•ฐๆฎๅบ“ๅ‘ฝไปค +# ============================================ + +db-migrate: + npx prisma migrate dev + +db-reset: + npx prisma migrate reset --force + +db-seed: + npx prisma db seed + +db-studio: + npx prisma studio diff --git a/backend/services/reward-service/README.md b/backend/services/reward-service/README.md new file mode 100644 index 00000000..8f0f65f7 --- /dev/null +++ b/backend/services/reward-service/README.md @@ -0,0 +1,98 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Project setup + +```bash +$ npm install +``` + +## Compile and run the project + +```bash +# development +$ npm run start + +# watch mode +$ npm run start:dev + +# production mode +$ npm run start:prod +``` + +## Run tests + +```bash +# unit tests +$ npm run test + +# e2e tests +$ npm run test:e2e + +# test coverage +$ npm run test:cov +``` + +## Deployment + +When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. + +If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: + +```bash +$ npm install -g @nestjs/mau +$ mau deploy +``` + +With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myล›liwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/backend/services/reward-service/docker-compose.test.yml b/backend/services/reward-service/docker-compose.test.yml new file mode 100644 index 00000000..ace0c9fa --- /dev/null +++ b/backend/services/reward-service/docker-compose.test.yml @@ -0,0 +1,74 @@ +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + container_name: reward-test-postgres + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: reward_test + ports: + - '5433:5432' + volumes: + - postgres-test-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U test -d reward_test'] + interval: 5s + timeout: 5s + retries: 5 + networks: + - reward-test-network + + redis: + image: redis:7-alpine + container_name: reward-test-redis + ports: + - '6380:6379' + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 5s + retries: 5 + networks: + - reward-test-network + + zookeeper: + image: confluentinc/cp-zookeeper:7.5.0 + container_name: reward-test-zookeeper + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + networks: + - reward-test-network + + kafka: + image: confluentinc/cp-kafka:7.5.0 + container_name: reward-test-kafka + depends_on: + - zookeeper + ports: + - '9093:9092' + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9093 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + healthcheck: + test: ['CMD-SHELL', 'kafka-broker-api-versions --bootstrap-server localhost:9092'] + interval: 10s + timeout: 10s + retries: 5 + networks: + - reward-test-network + +volumes: + postgres-test-data: + +networks: + reward-test-network: + name: reward-test-network + driver: bridge diff --git a/backend/services/reward-service/docs/API.md b/backend/services/reward-service/docs/API.md new file mode 100644 index 00000000..926d82db --- /dev/null +++ b/backend/services/reward-service/docs/API.md @@ -0,0 +1,426 @@ +# Reward Service API ๆŽฅๅฃๆ–‡ๆกฃ + +## ๆฆ‚่ฟฐ + +ๆœฌๆ–‡ๆกฃๆ่ฟฐ Reward Service ็š„ RESTful API ๆŽฅๅฃใ€‚ๆ‰€ๆœ‰ๆŽฅๅฃๅ‡้œ€่ฆ JWT ่ฎค่ฏ๏ผˆ้™คๅฅๅบทๆฃ€ๆŸฅๅค–๏ผ‰ใ€‚ + +### Base URL + +``` +http://localhost:3000 +``` + +### ่ฎค่ฏๆ–นๅผ + +ๆ‰€ๆœ‰ๅ—ไฟๆŠค็š„็ซฏ็‚น้ƒฝ้œ€่ฆๅœจ่ฏทๆฑ‚ๅคดไธญๆบๅธฆ JWT Token๏ผš + +``` +Authorization: Bearer +``` + +### ้€š็”จๅ“ๅบ”ๆ ผๅผ + +**ๆˆๅŠŸๅ“ๅบ”**: +```json +{ + "data": { ... }, + "pagination": { + "page": 1, + "pageSize": 20, + "total": 100 + } +} +``` + +**้”™่ฏฏๅ“ๅบ”**: +```json +{ + "statusCode": 400, + "message": "้”™่ฏฏๆ่ฟฐ", + "error": "Bad Request" +} +``` + +--- + +## API ็ซฏ็‚น + +### 1. ๅฅๅบทๆฃ€ๆŸฅ + +#### GET /health + +ๆฃ€ๆŸฅๆœๅŠกๅฅๅบท็Šถๆ€๏ผŒๆ— ้œ€่ฎค่ฏใ€‚ + +**่ฏทๆฑ‚**: +```http +GET /health HTTP/1.1 +Host: localhost:3000 +``` + +**ๅ“ๅบ”** (200 OK): +```json +{ + "status": "ok", + "service": "reward-service", + "timestamp": "2024-12-01T00:00:00.000Z" +} +``` + +--- + +### 2. ๅฅ–ๅŠฑๆŸฅ่ฏข + +#### GET /rewards/summary + +่Žทๅ–ๅฝ“ๅ‰็”จๆˆท็š„ๅฅ–ๅŠฑๆฑ‡ๆ€ปไฟกๆฏใ€‚ + +**่ฏทๆฑ‚**: +```http +GET /rewards/summary HTTP/1.1 +Host: localhost:3000 +Authorization: Bearer +``` + +**ๅ“ๅบ”** (200 OK): +```json +{ + "pendingUsdt": 1000, + "pendingHashpower": 0.5, + "pendingExpireAt": "2024-12-02T00:00:00.000Z", + "pendingRemainingTimeMs": 43200000, + "settleableUsdt": 500, + "settleableHashpower": 0.2, + "settledTotalUsdt": 2000, + "settledTotalHashpower": 1.0, + "expiredTotalUsdt": 100, + "expiredTotalHashpower": 0.1 +} +``` + +**ๅ“ๅบ”ๅญ—ๆฎต่ฏดๆ˜Ž**: + +| ๅญ—ๆฎต | ็ฑปๅž‹ | ๆ่ฟฐ | +|------|------|------| +| `pendingUsdt` | number | ๅพ…้ข†ๅ– USDT ้‡‘้ข | +| `pendingHashpower` | number | ๅพ…้ข†ๅ–็ฎ—ๅŠ› | +| `pendingExpireAt` | string \| null | ๆœ€่ฟ‘่ฟ‡ๆœŸๆ—ถ้—ด (ISO 8601) | +| `pendingRemainingTimeMs` | number | ๅ‰ฉไฝ™่ฟ‡ๆœŸๆฏซ็ง’ๆ•ฐ | +| `settleableUsdt` | number | ๅฏ็ป“็ฎ— USDT ้‡‘้ข | +| `settleableHashpower` | number | ๅฏ็ป“็ฎ—็ฎ—ๅŠ› | +| `settledTotalUsdt` | number | ็ดฏ่ฎกๅทฒ็ป“็ฎ— USDT | +| `settledTotalHashpower` | number | ็ดฏ่ฎกๅทฒ็ป“็ฎ—็ฎ—ๅŠ› | +| `expiredTotalUsdt` | number | ็ดฏ่ฎกๅทฒ่ฟ‡ๆœŸ USDT | +| `expiredTotalHashpower` | number | ็ดฏ่ฎกๅทฒ่ฟ‡ๆœŸ็ฎ—ๅŠ› | + +--- + +#### GET /rewards/details + +่Žทๅ–็”จๆˆทๅฅ–ๅŠฑๆ˜Ž็ป†๏ผŒๆ”ฏๆŒ็ญ›้€‰ๅ’Œๅˆ†้กตใ€‚ + +**่ฏทๆฑ‚**: +```http +GET /rewards/details?status=PENDING&page=1&pageSize=20 HTTP/1.1 +Host: localhost:3000 +Authorization: Bearer +``` + +**ๆŸฅ่ฏขๅ‚ๆ•ฐ**: + +| ๅ‚ๆ•ฐ | ็ฑปๅž‹ | ๅฟ…ๅกซ | ๆ่ฟฐ | +|------|------|------|------| +| `status` | string | ๅฆ | ๅฅ–ๅŠฑ็Šถๆ€็ญ›้€‰ | +| `rightType` | string | ๅฆ | ๆƒ็›Š็ฑปๅž‹็ญ›้€‰ | +| `page` | number | ๅฆ | ้กต็ ๏ผŒ้ป˜่ฎค 1 | +| `pageSize` | number | ๅฆ | ๆฏ้กตๆกๆ•ฐ๏ผŒ้ป˜่ฎค 20 | + +**status ๆžšไธพๅ€ผ**: +- `PENDING` - ๅพ…้ข†ๅ– +- `SETTLEABLE` - ๅฏ็ป“็ฎ— +- `SETTLED` - ๅทฒ็ป“็ฎ— +- `EXPIRED` - ๅทฒ่ฟ‡ๆœŸ + +**rightType ๆžšไธพๅ€ผ**: +- `SHARE_RIGHT` - ๅˆ†ไบซๆƒ็›Š (500 USDT) +- `PROVINCE_AREA_RIGHT` - ็œๅŒบๅŸŸๆƒ็›Š (15 USDT + 1% ็ฎ—ๅŠ›) +- `PROVINCE_TEAM_RIGHT` - ็œๅ›ข้˜Ÿๆƒ็›Š (20 USDT) +- `CITY_AREA_RIGHT` - ๅธ‚ๅŒบๅŸŸๆƒ็›Š (35 USDT + 2% ็ฎ—ๅŠ›) +- `CITY_TEAM_RIGHT` - ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š (40 USDT) +- `COMMUNITY_RIGHT` - ็คพๅŒบๆƒ็›Š (80 USDT) + +**ๅ“ๅบ”** (200 OK): +```json +{ + "data": [ + { + "id": "1", + "rightType": "SHARE_RIGHT", + "usdtAmount": 500, + "hashpowerAmount": 0, + "rewardStatus": "PENDING", + "createdAt": "2024-12-01T00:00:00.000Z", + "expireAt": "2024-12-02T00:00:00.000Z", + "remainingTimeMs": 86400000, + "claimedAt": null, + "settledAt": null, + "expiredAt": null, + "memo": "ๅˆ†ไบซๆƒ็›Š๏ผšๆฅ่‡ช็”จๆˆท100็š„่ฎค็ง" + } + ], + "pagination": { + "page": 1, + "pageSize": 20, + "total": 1 + } +} +``` + +**ๅ“ๅบ”ๅญ—ๆฎต่ฏดๆ˜Ž**: + +| ๅญ—ๆฎต | ็ฑปๅž‹ | ๆ่ฟฐ | +|------|------|------| +| `id` | string | ๅฅ–ๅŠฑๆตๆฐด ID | +| `rightType` | string | ๆƒ็›Š็ฑปๅž‹ | +| `usdtAmount` | number | USDT ้‡‘้ข | +| `hashpowerAmount` | number | ็ฎ—ๅŠ›้‡‘้ข | +| `rewardStatus` | string | ๅฅ–ๅŠฑ็Šถๆ€ | +| `createdAt` | string | ๅˆ›ๅปบๆ—ถ้—ด | +| `expireAt` | string \| null | ่ฟ‡ๆœŸๆ—ถ้—ด (ไป…ๅพ…้ข†ๅ–็Šถๆ€) | +| `remainingTimeMs` | number | ๅ‰ฉไฝ™่ฟ‡ๆœŸๆฏซ็ง’ๆ•ฐ | +| `claimedAt` | string \| null | ้ข†ๅ–ๆ—ถ้—ด | +| `settledAt` | string \| null | ็ป“็ฎ—ๆ—ถ้—ด | +| `expiredAt` | string \| null | ่ฟ‡ๆœŸๆ—ถ้—ด | +| `memo` | string | ๅค‡ๆณจไฟกๆฏ | + +--- + +#### GET /rewards/pending + +่Žทๅ–็”จๆˆทๅพ…้ข†ๅ–ๅฅ–ๅŠฑๅˆ—่กจ๏ผŒๅŒ…ๅซๅ€’่ฎกๆ—ถไฟกๆฏใ€‚ + +**่ฏทๆฑ‚**: +```http +GET /rewards/pending HTTP/1.1 +Host: localhost:3000 +Authorization: Bearer +``` + +**ๅ“ๅบ”** (200 OK): +```json +[ + { + "id": "1", + "rightType": "SHARE_RIGHT", + "usdtAmount": 500, + "hashpowerAmount": 0, + "createdAt": "2024-12-01T00:00:00.000Z", + "expireAt": "2024-12-02T00:00:00.000Z", + "remainingTimeMs": 43200000, + "memo": "ๅˆ†ไบซๆƒ็›Š๏ผšๆฅ่‡ช็”จๆˆท100็š„่ฎค็ง๏ผˆ24hๅ†…่ฎค็งๅฏ้ข†ๅ–๏ผ‰" + } +] +``` + +--- + +### 3. ๅฅ–ๅŠฑ็ป“็ฎ— + +#### POST /rewards/settle + +็ป“็ฎ—ๅฏ็ป“็ฎ—็š„ๅฅ–ๅŠฑ๏ผŒๆ”ฏๆŒๅคš็ง็›ฎๆ ‡ๅธ็งใ€‚ + +**่ฏทๆฑ‚**: +```http +POST /rewards/settle HTTP/1.1 +Host: localhost:3000 +Authorization: Bearer +Content-Type: application/json + +{ + "settleCurrency": "BNB" +} +``` + +**่ฏทๆฑ‚ไฝ“ๅ‚ๆ•ฐ**: + +| ๅ‚ๆ•ฐ | ็ฑปๅž‹ | ๅฟ…ๅกซ | ๆ่ฟฐ | +|------|------|------|------| +| `settleCurrency` | string | ๆ˜ฏ | ็›ฎๆ ‡็ป“็ฎ—ๅธ็ง | + +**ๆ”ฏๆŒ็š„็ป“็ฎ—ๅธ็ง**: +- `BNB` - ๅธๅฎ‰ๅธ +- `OG` - OG Token +- `USDT` - ๆณฐ่พพๅธ +- `DST` - DST Token + +**ๆˆๅŠŸๅ“ๅบ”** (201 Created): +```json +{ + "success": true, + "settledUsdtAmount": 500, + "receivedAmount": 0.25, + "settleCurrency": "BNB", + "txHash": "0x123abc..." +} +``` + +**ๅคฑ่ดฅๅ“ๅบ” - ๆ— ๅฏ็ป“็ฎ—ๅฅ–ๅŠฑ** (201 Created): +```json +{ + "success": false, + "settledUsdtAmount": 0, + "receivedAmount": 0, + "settleCurrency": "BNB", + "error": "ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š" +} +``` + +**ๅคฑ่ดฅๅ“ๅบ” - ้’ฑๅŒ…ๆœๅŠก้”™่ฏฏ** (201 Created): +```json +{ + "success": false, + "settledUsdtAmount": 500, + "receivedAmount": 0, + "settleCurrency": "BNB", + "error": "Insufficient liquidity" +} +``` + +**้ชŒ่ฏ้”™่ฏฏ** (400 Bad Request): +```json +{ + "statusCode": 400, + "message": ["settleCurrency should not be empty"], + "error": "Bad Request" +} +``` + +**ๅ“ๅบ”ๅญ—ๆฎต่ฏดๆ˜Ž**: + +| ๅญ—ๆฎต | ็ฑปๅž‹ | ๆ่ฟฐ | +|------|------|------| +| `success` | boolean | ็ป“็ฎ—ๆ˜ฏๅฆๆˆๅŠŸ | +| `settledUsdtAmount` | number | ็ป“็ฎ—็š„ USDT ้‡‘้ข | +| `receivedAmount` | number | ๆ”ถๅˆฐ็š„็›ฎๆ ‡ๅธ็ง้‡‘้ข | +| `settleCurrency` | string | ็›ฎๆ ‡็ป“็ฎ—ๅธ็ง | +| `txHash` | string \| undefined | ไบคๆ˜“ๅ“ˆๅธŒ (ๆˆๅŠŸๆ—ถ่ฟ”ๅ›ž) | +| `error` | string \| undefined | ้”™่ฏฏไฟกๆฏ (ๅคฑ่ดฅๆ—ถ่ฟ”ๅ›ž) | + +--- + +## ้”™่ฏฏ็  + +| HTTP Status | ้”™่ฏฏ็  | ๆ่ฟฐ | +|-------------|--------|------| +| 400 | Bad Request | ่ฏทๆฑ‚ๅ‚ๆ•ฐ้ชŒ่ฏๅคฑ่ดฅ | +| 401 | Unauthorized | ๆœช่ฎค่ฏๆˆ– Token ๆ— ๆ•ˆ | +| 403 | Forbidden | ๆ— ๆƒ่ฎฟ้—ฎ | +| 404 | Not Found | ่ต„ๆบไธๅญ˜ๅœจ | +| 500 | Internal Server Error | ๆœๅŠกๅ™จๅ†…้ƒจ้”™่ฏฏ | + +--- + +## ๆ•ฐๆฎ็ฑปๅž‹ๅฎšไน‰ + +### RewardStatus + +```typescript +enum RewardStatus { + PENDING = 'PENDING', // ๅพ…้ข†ๅ– (24hๅ€’่ฎกๆ—ถ) + SETTLEABLE = 'SETTLEABLE', // ๅฏ็ป“็ฎ— + SETTLED = 'SETTLED', // ๅทฒ็ป“็ฎ— + EXPIRED = 'EXPIRED', // ๅทฒ่ฟ‡ๆœŸ +} +``` + +### RightType + +```typescript +enum RightType { + SHARE_RIGHT = 'SHARE_RIGHT', // ๅˆ†ไบซๆƒ็›Š 500U + PROVINCE_AREA_RIGHT = 'PROVINCE_AREA_RIGHT',// ็œๅŒบๅŸŸๆƒ็›Š 15U + 1%็ฎ—ๅŠ› + PROVINCE_TEAM_RIGHT = 'PROVINCE_TEAM_RIGHT',// ็œๅ›ข้˜Ÿๆƒ็›Š 20U + CITY_AREA_RIGHT = 'CITY_AREA_RIGHT', // ๅธ‚ๅŒบๅŸŸๆƒ็›Š 35U + 2%็ฎ—ๅŠ› + CITY_TEAM_RIGHT = 'CITY_TEAM_RIGHT', // ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š 40U + COMMUNITY_RIGHT = 'COMMUNITY_RIGHT', // ็คพๅŒบๆƒ็›Š 80U +} +``` + +### ๆƒ็›Š้‡‘้ข้…็ฝฎ + +```typescript +const RIGHT_AMOUNTS = { + SHARE_RIGHT: { usdt: 500, hashpowerPercent: 0 }, + PROVINCE_AREA_RIGHT: { usdt: 15, hashpowerPercent: 1 }, + PROVINCE_TEAM_RIGHT: { usdt: 20, hashpowerPercent: 0 }, + CITY_AREA_RIGHT: { usdt: 35, hashpowerPercent: 2 }, + CITY_TEAM_RIGHT: { usdt: 40, hashpowerPercent: 0 }, + COMMUNITY_RIGHT: { usdt: 80, hashpowerPercent: 0 }, +}; +``` + +--- + +## Swagger ๆ–‡ๆกฃ + +ๆœๅŠกๅฏๅŠจๅŽ๏ผŒๅฏ้€š่ฟ‡ไปฅไธ‹ๅœฐๅ€่ฎฟ้—ฎ Swagger UI๏ผš + +``` +http://localhost:3000/api +``` + +--- + +## ไฝฟ็”จ็คบไพ‹ + +### cURL ็คบไพ‹ + +```bash +# ๅฅๅบทๆฃ€ๆŸฅ +curl http://localhost:3000/health + +# ่Žทๅ–ๅฅ–ๅŠฑๆฑ‡ๆ€ป +curl -H "Authorization: Bearer " \ + http://localhost:3000/rewards/summary + +# ่Žทๅ–ๅฅ–ๅŠฑๆ˜Ž็ป† (็ญ›้€‰ๅพ…้ข†ๅ–) +curl -H "Authorization: Bearer " \ + "http://localhost:3000/rewards/details?status=PENDING&page=1&pageSize=10" + +# ็ป“็ฎ—ๅฅ–ๅŠฑ +curl -X POST \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"settleCurrency": "BNB"}' \ + http://localhost:3000/rewards/settle +``` + +### JavaScript ็คบไพ‹ + +```javascript +const BASE_URL = 'http://localhost:3000'; +const TOKEN = 'your-jwt-token'; + +// ่Žทๅ–ๅฅ–ๅŠฑๆฑ‡ๆ€ป +async function getRewardSummary() { + const response = await fetch(`${BASE_URL}/rewards/summary`, { + headers: { + 'Authorization': `Bearer ${TOKEN}`, + }, + }); + return response.json(); +} + +// ็ป“็ฎ—ๅฅ–ๅŠฑ +async function settleRewards(currency) { + const response = await fetch(`${BASE_URL}/rewards/settle`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ settleCurrency: currency }), + }); + return response.json(); +} +``` diff --git a/backend/services/reward-service/docs/ARCHITECTURE.md b/backend/services/reward-service/docs/ARCHITECTURE.md new file mode 100644 index 00000000..c4ebce2d --- /dev/null +++ b/backend/services/reward-service/docs/ARCHITECTURE.md @@ -0,0 +1,511 @@ +# Reward Service ๆžถๆž„ๆฆ‚่งˆ + +## ๆœๅŠกๆฆ‚่ฟฐ + +Reward Service ๆ˜ฏ RWA Durian Queen ๅนณๅฐ็š„ๆ ธๅฟƒๆ”ถ็›Šๅˆ†้…ๆœๅŠก๏ผŒ่ดŸ่ดฃๅค„็†็”จๆˆท่ฎค็งๆฆด่Žฒๆ ‘ๅŽ็š„ๆ”ถ็›Š่ฎก็ฎ—ใ€ๅˆ†้…ใ€้ข†ๅ–ๅ’Œ็ป“็ฎ—ใ€‚ + +### ๆŠ€ๆœฏๆ ˆ + +- **ๆก†ๆžถ**: NestJS 11.x +- **่ฏญ่จ€**: TypeScript 5.7 +- **ๆ•ฐๆฎๅบ“**: PostgreSQL 15 (้€š่ฟ‡ Prisma 7.x ORM) +- **็ผ“ๅญ˜**: Redis 7.x +- **ๆถˆๆฏ้˜Ÿๅˆ—**: Apache Kafka +- **ๆต‹่ฏ•**: Jest 30.x + Supertest 7.x +- **APIๆ–‡ๆกฃ**: Swagger/OpenAPI + +--- + +## ้ข†ๅŸŸ้ฉฑๅŠจ่ฎพ่ฎก (DDD) ๆžถๆž„ + +ๆœฌๆœๅŠก้‡‡็”จ้ข†ๅŸŸ้ฉฑๅŠจ่ฎพ่ฎกๆžถๆž„๏ผŒๅˆ†ไธบไปฅไธ‹ๅ››ๅฑ‚๏ผš + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ API Layer โ”‚ +โ”‚ Controllers, DTOs, Guards โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Application Layer โ”‚ +โ”‚ Application Services, Schedulers, Use Cases โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Domain Layer โ”‚ +โ”‚ Aggregates, Value Objects, Domain Events, Domain Services โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Infrastructure Layer โ”‚ +โ”‚ Repositories, External Clients, Kafka, Redis โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ็›ฎๅฝ•็ป“ๆž„ + +``` +src/ +โ”œโ”€โ”€ api/ # APIๅฑ‚ +โ”‚ โ”œโ”€โ”€ controllers/ # ๆŽงๅˆถๅ™จ +โ”‚ โ”‚ โ”œโ”€โ”€ health.controller.ts +โ”‚ โ”‚ โ”œโ”€โ”€ reward.controller.ts +โ”‚ โ”‚ โ””โ”€โ”€ settlement.controller.ts +โ”‚ โ””โ”€โ”€ dto/ # ๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก +โ”‚ โ”œโ”€โ”€ request/ +โ”‚ โ””โ”€โ”€ response/ +โ”‚ +โ”œโ”€โ”€ application/ # ๅบ”็”จๅฑ‚ +โ”‚ โ”œโ”€โ”€ services/ +โ”‚ โ”‚ โ””โ”€โ”€ reward-application.service.ts +โ”‚ โ””โ”€โ”€ schedulers/ +โ”‚ โ””โ”€โ”€ reward-expiration.scheduler.ts +โ”‚ +โ”œโ”€โ”€ domain/ # ้ข†ๅŸŸๅฑ‚ (ๆ ธๅฟƒ) +โ”‚ โ”œโ”€โ”€ aggregates/ # ่šๅˆๆ น +โ”‚ โ”‚ โ”œโ”€โ”€ reward-ledger-entry/ +โ”‚ โ”‚ โ””โ”€โ”€ reward-summary/ +โ”‚ โ”œโ”€โ”€ value-objects/ # ๅ€ผๅฏน่ฑก +โ”‚ โ”‚ โ”œโ”€โ”€ money.vo.ts +โ”‚ โ”‚ โ”œโ”€โ”€ hashpower.vo.ts +โ”‚ โ”‚ โ”œโ”€โ”€ reward-source.vo.ts +โ”‚ โ”‚ โ”œโ”€โ”€ reward-status.enum.ts +โ”‚ โ”‚ โ””โ”€โ”€ right-type.enum.ts +โ”‚ โ”œโ”€โ”€ events/ # ้ข†ๅŸŸไบ‹ไปถ +โ”‚ โ”‚ โ”œโ”€โ”€ reward-created.event.ts +โ”‚ โ”‚ โ”œโ”€โ”€ reward-claimed.event.ts +โ”‚ โ”‚ โ”œโ”€โ”€ reward-expired.event.ts +โ”‚ โ”‚ โ””โ”€โ”€ reward-settled.event.ts +โ”‚ โ”œโ”€โ”€ services/ # ้ข†ๅŸŸๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ reward-calculation.service.ts +โ”‚ โ”‚ โ””โ”€โ”€ reward-expiration.service.ts +โ”‚ โ””โ”€โ”€ repositories/ # ไป“ๅ‚จๆŽฅๅฃ +โ”‚ +โ”œโ”€โ”€ infrastructure/ # ๅŸบ็ก€่ฎพๆ–ฝๅฑ‚ +โ”‚ โ”œโ”€โ”€ persistence/ +โ”‚ โ”‚ โ”œโ”€โ”€ prisma/ +โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ # ไป“ๅ‚จๅฎž็Žฐ +โ”‚ โ”‚ โ””โ”€โ”€ mappers/ # ๆŒไน…ๅŒ–ๆ˜ ๅฐ„ +โ”‚ โ”œโ”€โ”€ external/ # ๅค–้ƒจๆœๅŠกๅฎขๆˆท็ซฏ +โ”‚ โ”‚ โ”œโ”€โ”€ referral-service/ +โ”‚ โ”‚ โ”œโ”€โ”€ authorization-service/ +โ”‚ โ”‚ โ””โ”€โ”€ wallet-service/ +โ”‚ โ”œโ”€โ”€ kafka/ # Kafkaๆถˆๆฏ +โ”‚ โ””โ”€โ”€ redis/ # Redis็ผ“ๅญ˜ +โ”‚ +โ”œโ”€โ”€ shared/ # ๅ…ฑไบซๆจกๅ— +โ”‚ โ”œโ”€โ”€ guards/ +โ”‚ โ””โ”€โ”€ strategies/ +โ”‚ +โ””โ”€โ”€ config/ # ้…็ฝฎ +``` + +--- + +## ๆ ธๅฟƒ้ข†ๅŸŸๆจกๅž‹ + +### ่šๅˆๆ น + +#### 1. RewardLedgerEntry (ๅฅ–ๅŠฑๆตๆฐด) + +ๅฅ–ๅŠฑๆตๆฐดๆ˜ฏๆ ธๅฟƒ่šๅˆๆ น๏ผŒ่ฎฐๅฝ•ๆฏไธ€็ฌ”ๅฅ–ๅŠฑ็š„่ฏฆ็ป†ไฟกๆฏๅ’Œ็Šถๆ€ๅ˜ๅŒ–ใ€‚ + +```typescript +class RewardLedgerEntry { + // ่บซไปฝ + id: bigint + userId: bigint + rewardSource: RewardSource + + // ้‡‘้ข + usdtAmount: Money + hashpowerAmount: Hashpower + + // ็Šถๆ€ + rewardStatus: RewardStatus // PENDING | SETTLEABLE | SETTLED | EXPIRED + + // ๆ—ถ้—ดๆˆณ + createdAt: Date + expireAt: Date | null // ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ24hๅŽ่ฟ‡ๆœŸ + claimedAt: Date | null + settledAt: Date | null + expiredAt: Date | null + + // ่กŒไธบ + claim(): void // ้ข†ๅ– (PENDING โ†’ SETTLEABLE) + expire(): void // ่ฟ‡ๆœŸ (PENDING โ†’ EXPIRED) + settle(): void // ็ป“็ฎ— (SETTLEABLE โ†’ SETTLED) +} +``` + +**็Šถๆ€ๆœบ**: + +``` + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ PENDING โ”‚ + โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ็”จๆˆท่ฎค็ง โ”‚ โ”‚ 24h่ถ…ๆ—ถ + โ–ผ โ”‚ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ SETTLEABLEโ”‚ โ”‚ โ”‚ EXPIRED โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ”‚ ็”จๆˆท็ป“็ฎ— โ”‚ + โ–ผ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ”‚ SETTLED โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +``` + +#### 2. RewardSummary (ๅฅ–ๅŠฑๆฑ‡ๆ€ป) + +็”จๆˆท็ปดๅบฆ็š„ๅฅ–ๅŠฑๆฑ‡ๆ€ป๏ผŒๆไพ›ๅฟซ้€ŸๆŸฅ่ฏข่ƒฝๅŠ›ใ€‚ + +```typescript +class RewardSummary { + userId: bigint + + // ๅพ…้ข†ๅ– (24hๅ€’่ฎกๆ—ถ) + pendingUsdt: Money + pendingHashpower: Hashpower + pendingExpireAt: Date | null + + // ๅฏ็ป“็ฎ— + settleableUsdt: Money + settleableHashpower: Hashpower + + // ็ดฏ่ฎกๅทฒ็ป“็ฎ— + settledTotalUsdt: Money + settledTotalHashpower: Hashpower + + // ็ดฏ่ฎกๅทฒ่ฟ‡ๆœŸ + expiredTotalUsdt: Money + expiredTotalHashpower: Hashpower +} +``` + +### ๅ€ผๅฏน่ฑก + +| ๅ€ผๅฏน่ฑก | ๆ่ฟฐ | ไธๅ˜ๅผ | +|--------|------|--------| +| `Money` | ่ดงๅธ้‡‘้ข | ้‡‘้ข >= 0, ่ดงๅธ็ฑปๅž‹ไธๅฏๅ˜ | +| `Hashpower` | ็ฎ—ๅŠ› | ๅ€ผ >= 0 | +| `RewardSource` | ๅฅ–ๅŠฑๆฅๆบ | ๅ…ณ่”่ฎขๅ•ๅ’Œ็”จๆˆทไธๅฏๅ˜ | +| `RewardStatus` | ๅฅ–ๅŠฑ็Šถๆ€ | ๆžšไธพๅ€ผ | +| `RightType` | ๆƒ็›Š็ฑปๅž‹ | ๆžšไธพๅ€ผ | + +--- + +## ๆ”ถ็›Š็ฑปๅž‹ + +็ณป็ปŸๆ”ฏๆŒ6็งๆ”ถ็›Š็ฑปๅž‹๏ผŒๆฏ็งๆœ‰ไธๅŒ็š„่ฎก็ฎ—่ง„ๅˆ™๏ผš + +| ๆƒ็›Š็ฑปๅž‹ | USDT้‡‘้ข | ็ฎ—ๅŠ›็™พๅˆ†ๆฏ” | ๆŽฅๆ”ถๆ–น | +|---------|---------|-----------|--------| +| ๅˆ†ไบซๆƒ็›Š (SHARE_RIGHT) | 500 U/ๆฃต | 0% | ็›ดๆŽฅๆŽจ่ไบบ | +| ็œๅŒบๅŸŸๆƒ็›Š (PROVINCE_AREA_RIGHT) | 15 U/ๆฃต | 1% | ็ณป็ปŸ็œๅ…ฌๅธ | +| ็œๅ›ข้˜Ÿๆƒ็›Š (PROVINCE_TEAM_RIGHT) | 20 U/ๆฃต | 0% | ๆŽˆๆƒ็œๅ…ฌๅธ | +| ๅธ‚ๅŒบๅŸŸๆƒ็›Š (CITY_AREA_RIGHT) | 35 U/ๆฃต | 2% | ็ณป็ปŸๅธ‚ๅ…ฌๅธ | +| ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š (CITY_TEAM_RIGHT) | 40 U/ๆฃต | 0% | ๆŽˆๆƒๅธ‚ๅ…ฌๅธ | +| ็คพๅŒบๆƒ็›Š (COMMUNITY_RIGHT) | 80 U/ๆฃต | 0% | ๆ‰€ๅฑž็คพๅŒบ | + +**ๆ€ป่ฎก**: 690 USDT + 3% ็ฎ—ๅŠ› / ๆฃต + +--- + +## ๆ ธๅฟƒไธšๅŠกๆต็จ‹ + +### 1. ๅฅ–ๅŠฑๅˆ†้…ๆต็จ‹ + +``` +่ฎขๅ•ๆ”ฏไป˜ๆˆๅŠŸไบ‹ไปถ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ่ฎก็ฎ—6็งๆƒ็›Šๅฅ–ๅŠฑ โ”‚ +โ”‚ RewardCalculationโ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ไฟๅญ˜ๅฅ–ๅŠฑๆตๆฐด โ”‚ +โ”‚ RewardLedgerEntryโ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๆ›ดๆ–ฐ็”จๆˆทๆฑ‡ๆ€ป โ”‚ +โ”‚ RewardSummary โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๅ‘ๅธƒ้ข†ๅŸŸไบ‹ไปถ โ”‚ +โ”‚ Kafka โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 2. ๅˆ†ไบซๆƒ็›Š24ๅฐๆ—ถๆœบๅˆถ + +``` +ๆŽจ่ไบบๅทฒ่ฎค็ง๏ผŸ + โ”‚ + โ”œโ”€โ”€ ๆ˜ฏ โ†’ ็›ดๆŽฅๅฏ็ป“็ฎ— (SETTLEABLE) + โ”‚ + โ””โ”€โ”€ ๅฆ โ†’ ๅพ…้ข†ๅ– (PENDING, 24hๅ€’่ฎกๆ—ถ) + โ”‚ + โ”œโ”€โ”€ 24hๅ†…่ฎค็ง โ†’ claim() โ†’ ๅฏ็ป“็ฎ— + โ”‚ + โ””โ”€โ”€ 24h่ถ…ๆ—ถ โ†’ expire() โ†’ ่ฟ›ๆ€ป้ƒจ็คพๅŒบ +``` + +### 3. ็ป“็ฎ—ๆต็จ‹ + +``` +็”จๆˆทๅ‘่ตท็ป“็ฎ—่ฏทๆฑ‚ (้€‰ๆ‹ฉBNB/OG/USDT/DST) + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ่Žทๅ–ๅฏ็ป“็ฎ—ๅฅ–ๅŠฑ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ่ฐƒ็”จ้’ฑๅŒ…ๆœๅŠก โ”‚ + โ”‚ ๆ‰ง่กŒSWAP โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ๆ›ดๆ–ฐๅฅ–ๅŠฑ็Šถๆ€ โ”‚ + โ”‚ SETTLED โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ ๅ‘ๅธƒ็ป“็ฎ—ไบ‹ไปถ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๅค–้ƒจๆœๅŠก้›†ๆˆ + +### ้˜ฒ่…ๅฑ‚่ฎพ่ฎก + +้€š่ฟ‡ๆŽฅๅฃๅฎšไน‰ไธŽๅค–้ƒจๆœๅŠก่งฃ่€ฆ๏ผš + +```typescript +// ๆŽจ่ๅ…ณ็ณปๆœๅŠก +interface IReferralServiceClient { + getReferralChain(userId: bigint): Promise<{ + ancestors: Array<{ userId: bigint; hasPlanted: boolean }>; + }>; +} + +// ๆŽˆๆƒไฝ“็ณปๆœๅŠก +interface IAuthorizationServiceClient { + findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise; + findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise; + findNearestCommunity(userId: bigint): Promise; +} + +// ้’ฑๅŒ…ๆœๅŠก +interface IWalletServiceClient { + executeSwap(params: { + userId: bigint; + usdtAmount: number; + targetCurrency: string; + }): Promise; +} +``` + +### ๆœๅŠกไพ่ต– + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Planting Service โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ โ”‚ +โ”‚ (่ฎค็งๆœๅŠก) โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ + โ”‚ Reward Service โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ Referral Service โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ โ”‚ +โ”‚ (ๆŽจ่ๆœๅŠก) โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ + โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚Authorization Svc โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ โ”‚ +โ”‚ (ๆŽˆๆƒๆœๅŠก) โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Wallet Service โ”‚ + โ”‚ (้’ฑๅŒ…ๆœๅŠก) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๆถˆๆฏไธŽไบ‹ไปถ + +### Kafka Topics + +| Topic | ๆ่ฟฐ | ็”Ÿไบง่€… | ๆถˆ่ดน่€… | +|-------|------|--------|--------| +| `planting.order.paid` | ่ฎค็ง่ฎขๅ•ๆ”ฏไป˜ๆˆๅŠŸ | Planting Service | Reward Service | +| `planting.user.planted` | ็”จๆˆทๅฎŒๆˆ่ฎค็ง | Planting Service | Reward Service | +| `reward.created` | ๅฅ–ๅŠฑๅˆ›ๅปบ | Reward Service | - | +| `reward.claimed` | ๅฅ–ๅŠฑ้ข†ๅ– | Reward Service | - | +| `reward.settled` | ๅฅ–ๅŠฑ็ป“็ฎ— | Reward Service | Wallet Service | +| `reward.expired` | ๅฅ–ๅŠฑ่ฟ‡ๆœŸ | Reward Service | - | + +### ้ข†ๅŸŸไบ‹ไปถ + +```typescript +// ๅฅ–ๅŠฑๅˆ›ๅปบไบ‹ไปถ +interface RewardCreatedEvent { + entryId: string; + userId: string; + sourceOrderId: string; + rightType: RightType; + usdtAmount: number; + hashpowerAmount: number; + rewardStatus: RewardStatus; + expireAt: Date | null; +} + +// ๅฅ–ๅŠฑ้ข†ๅ–ไบ‹ไปถ +interface RewardClaimedEvent { + entryId: string; + userId: string; + usdtAmount: number; + hashpowerAmount: number; +} + +// ๅฅ–ๅŠฑ็ป“็ฎ—ไบ‹ไปถ +interface RewardSettledEvent { + entryId: string; + userId: string; + usdtAmount: number; + settleCurrency: string; + receivedAmount: number; +} + +// ๅฅ–ๅŠฑ่ฟ‡ๆœŸไบ‹ไปถ +interface RewardExpiredEvent { + entryId: string; + userId: string; + usdtAmount: number; + transferredTo: string; // 'HEADQUARTERS_COMMUNITY' +} +``` + +--- + +## ๅฎšๆ—ถไปปๅŠก + +### ๅฅ–ๅŠฑ่ฟ‡ๆœŸๆฃ€ๆŸฅ + +```typescript +@Cron('0 * * * * *') // ๆฏๅˆ†้’Ÿๆ‰ง่กŒ +async handleExpiredRewards() { + await this.rewardApplicationService.expireOverdueRewards(); +} +``` + +ๅค„็†้€ป่พ‘: +1. ๆŸฅๆ‰พๆ‰€ๆœ‰่ฟ‡ๆœŸ็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ +2. ๅฐ†ๅฅ–ๅŠฑ็Šถๆ€ๆ”นไธบๅทฒ่ฟ‡ๆœŸ +3. ๆ›ดๆ–ฐๅŽŸ็”จๆˆท็š„ๆฑ‡ๆ€ปๆ•ฐๆฎ +4. ๅฐ†้‡‘้ข่ฝฌๅ…ฅๆ€ป้ƒจ็คพๅŒบ่ดฆๆˆท +5. ๅ‘ๅธƒ่ฟ‡ๆœŸไบ‹ไปถ + +--- + +## ๆ•ฐๆฎๅบ“่ฎพ่ฎก + +### ๆ ธๅฟƒ่กจ็ป“ๆž„ + +```sql +-- ๅฅ–ๅŠฑๆตๆฐด่กจ +CREATE TABLE reward_ledger_entries ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + source_order_id BIGINT NOT NULL, + source_user_id BIGINT NOT NULL, + right_type VARCHAR(50) NOT NULL, + usdt_amount DECIMAL(18,2) NOT NULL, + hashpower_amount DECIMAL(18,8) NOT NULL, + reward_status VARCHAR(20) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + expire_at TIMESTAMP, + claimed_at TIMESTAMP, + settled_at TIMESTAMP, + expired_at TIMESTAMP, + memo TEXT, + + INDEX idx_user_status (user_id, reward_status), + INDEX idx_expire_at (expire_at) WHERE reward_status = 'PENDING' +); + +-- ็”จๆˆทๅฅ–ๅŠฑๆฑ‡ๆ€ป่กจ +CREATE TABLE reward_summaries ( + user_id BIGINT PRIMARY KEY, + pending_usdt DECIMAL(18,2) NOT NULL DEFAULT 0, + pending_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0, + pending_expire_at TIMESTAMP, + settleable_usdt DECIMAL(18,2) NOT NULL DEFAULT 0, + settleable_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0, + settled_total_usdt DECIMAL(18,2) NOT NULL DEFAULT 0, + settled_total_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0, + expired_total_usdt DECIMAL(18,2) NOT NULL DEFAULT 0, + expired_total_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0, + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); +``` + +--- + +## ๅฎ‰ๅ…จ่ฎพ่ฎก + +### ่ฎค่ฏไธŽๆŽˆๆƒ + +- **JWT่ฎค่ฏ**: ๆ‰€ๆœ‰API่ฏทๆฑ‚้œ€่ฆๆบๅธฆๆœ‰ๆ•ˆ็š„JWT Token +- **็”จๆˆท้š”็ฆป**: ็”จๆˆทๅช่ƒฝ่ฎฟ้—ฎ่‡ชๅทฑ็š„ๅฅ–ๅŠฑๆ•ฐๆฎ +- **Passport็ญ–็•ฅ**: ไฝฟ็”จ`passport-jwt`่ฟ›่กŒToken้ชŒ่ฏ + +```typescript +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET'), + }); + } +} +``` + +### ๆ•ฐๆฎๅฎŒๆ•ดๆ€ง + +- ไฝฟ็”จๆ•ฐๆฎๅบ“ไบ‹ๅŠกไฟ่ฏๆ•ฐๆฎไธ€่‡ดๆ€ง +- ้ข†ๅŸŸๆจกๅž‹ไธๅ˜ๅผๆฃ€ๆŸฅ +- ็Šถๆ€ๆœบ็บฆๆŸ้˜ฒๆญข้žๆณ•็Šถๆ€่ฝฌๆข + +--- + +## ๆ€ง่ƒฝไผ˜ๅŒ– + +### ็ผ“ๅญ˜็ญ–็•ฅ + +- Redis็ผ“ๅญ˜็”จๆˆทๅฅ–ๅŠฑๆฑ‡ๆ€ป +- ็ƒญ็‚นๆ•ฐๆฎ้ข„ๅŠ ่ฝฝ + +### ๆ•ฐๆฎๅบ“ไผ˜ๅŒ– + +- ๅคๅˆ็ดขๅผ•ไผ˜ๅŒ–ๆŸฅ่ฏข +- ๅˆ†้กตๆŸฅ่ฏข้ฟๅ…ๅ…จ่กจๆ‰ซๆ +- ๆ‰น้‡ๅ†™ๅ…ฅๅ‡ๅฐ‘IO + +### ๆถˆๆฏๅค„็† + +- Kafkaๆถˆ่ดน่€…็ป„ๅฎž็Žฐ่ดŸ่ฝฝๅ‡่กก +- ๅน‚็ญ‰ๆ€ง่ฎพ่ฎก้˜ฒๆญข้‡ๅคๅค„็† diff --git a/backend/services/reward-service/docs/DEPLOYMENT.md b/backend/services/reward-service/docs/DEPLOYMENT.md new file mode 100644 index 00000000..0da2a1e2 --- /dev/null +++ b/backend/services/reward-service/docs/DEPLOYMENT.md @@ -0,0 +1,679 @@ +# Reward Service ้ƒจ็ฝฒๆŒ‡ๅ— + +## ้ƒจ็ฝฒๆฆ‚่ฟฐ + +ๆœฌๆ–‡ๆกฃๆ่ฟฐ Reward Service ็š„้ƒจ็ฝฒๆžถๆž„ๅ’Œๆ“ไฝœๆŒ‡ๅ—ใ€‚ + +### ้ƒจ็ฝฒๆžถๆž„ + +``` + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Load Balancer โ”‚ + โ”‚ (Nginx/ALB) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Reward Svc โ”‚ โ”‚ Reward Svc โ”‚ โ”‚ Reward Svc โ”‚ + โ”‚ Instance 1 โ”‚ โ”‚ Instance 2 โ”‚ โ”‚ Instance 3 โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚PostgreSQLโ”‚ โ”‚ Redis โ”‚ โ”‚ Kafka โ”‚ +โ”‚ Primary โ”‚ โ”‚ Cluster โ”‚ โ”‚ Cluster โ”‚ +โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” +โ”‚PostgreSQLโ”‚ +โ”‚ Replica โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ็Žฏๅขƒ่ฆๆฑ‚ + +### ็”Ÿไบง็Žฏๅขƒ้…็ฝฎ + +| ็ป„ไปถ | ๆœ€ไฝŽ้…็ฝฎ | ๆŽจ่้…็ฝฎ | +|------|---------|---------| +| CPU | 2 vCPU | 4 vCPU | +| ๅ†…ๅญ˜ | 4 GB | 8 GB | +| ๅญ˜ๅ‚จ | 50 GB SSD | 100 GB SSD | +| Node.js | 20.x LTS | 20.x LTS | + +### ๅŸบ็ก€่ฎพๆ–ฝ่ฆๆฑ‚ + +| ๆœๅŠก | ็‰ˆๆœฌ | ่ฏดๆ˜Ž | +|------|------|------| +| PostgreSQL | 15.x | ไธปๆ•ฐๆฎๅบ“ | +| Redis | 7.x | ็ผ“ๅญ˜ๅ’Œไผš่ฏ | +| Apache Kafka | 3.x | ๆถˆๆฏ้˜Ÿๅˆ— | + +--- + +## Docker ้ƒจ็ฝฒ + +### Dockerfile + +```dockerfile +# ๆž„ๅปบ้˜ถๆฎต +FROM node:20-alpine AS builder + +WORKDIR /app + +# ๅคๅˆถไพ่ต–ๆ–‡ไปถ +COPY package*.json ./ +COPY prisma ./prisma/ + +# ๅฎ‰่ฃ…ไพ่ต– +RUN npm ci + +# ็”Ÿๆˆ Prisma Client +RUN npx prisma generate + +# ๅคๅˆถๆบไปฃ็  +COPY . . + +# ๆž„ๅปบ +RUN npm run build + +# ็”Ÿไบง้˜ถๆฎต +FROM node:20-alpine AS production + +WORKDIR /app + +# ๅฎ‰่ฃ…็”Ÿไบงไพ่ต– +COPY package*.json ./ +RUN npm ci --only=production + +# ๅคๅˆถๆž„ๅปบไบง็‰ฉ +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma + +# ่ฎพ็ฝฎ็Žฏๅขƒๅ˜้‡ +ENV NODE_ENV=production +ENV PORT=3000 + +# ๅฅๅบทๆฃ€ๆŸฅ +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 + +# ๆšด้œฒ็ซฏๅฃ +EXPOSE 3000 + +# ๅฏๅŠจๅ‘ฝไปค +CMD ["node", "dist/main.js"] +``` + +### docker-compose.yml (็”Ÿไบง) + +```yaml +version: '3.8' + +services: + reward-service: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DATABASE_URL=${DATABASE_URL} + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - KAFKA_BROKERS=${KAFKA_BROKERS} + - JWT_SECRET=${JWT_SECRET} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + kafka: + condition: service_healthy + deploy: + replicas: 3 + resources: + limits: + cpus: '2' + memory: 4G + reservations: + cpus: '1' + memory: 2G + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + + postgres: + image: postgres:15-alpine + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + volumes: + - redis-data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + kafka: + image: confluentinc/cp-kafka:7.5.0 + depends_on: + - zookeeper + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + healthcheck: + test: ["CMD-SHELL", "kafka-broker-api-versions --bootstrap-server localhost:9092"] + interval: 10s + timeout: 10s + retries: 5 + + zookeeper: + image: confluentinc/cp-zookeeper:7.5.0 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + +volumes: + postgres-data: + redis-data: +``` + +### ๆž„ๅปบๅ’ŒๆŽจ้€้•œๅƒ + +```bash +# ๆž„ๅปบ้•œๅƒ +docker build -t reward-service:latest . + +# ๆ ‡่ฎฐ้•œๅƒ +docker tag reward-service:latest your-registry/reward-service:v1.0.0 + +# ๆŽจ้€ๅˆฐ้•œๅƒไป“ๅบ“ +docker push your-registry/reward-service:v1.0.0 +``` + +--- + +## Kubernetes ้ƒจ็ฝฒ + +### Deployment + +```yaml +# k8s/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reward-service + namespace: rwadurian + labels: + app: reward-service +spec: + replicas: 3 + selector: + matchLabels: + app: reward-service + template: + metadata: + labels: + app: reward-service + spec: + containers: + - name: reward-service + image: your-registry/reward-service:v1.0.0 + ports: + - containerPort: 3000 + env: + - name: NODE_ENV + value: "production" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: reward-service-secrets + key: database-url + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: reward-service-config + key: redis-host + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: reward-service-secrets + key: jwt-secret + resources: + requests: + cpu: "500m" + memory: "512Mi" + limits: + cpu: "2000m" + memory: "4Gi" + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +### Service + +```yaml +# k8s/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: reward-service + namespace: rwadurian +spec: + selector: + app: reward-service + ports: + - protocol: TCP + port: 80 + targetPort: 3000 + type: ClusterIP +``` + +### Ingress + +```yaml +# k8s/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: reward-service-ingress + namespace: rwadurian + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: api.rwadurian.com + http: + paths: + - path: /rewards + pathType: Prefix + backend: + service: + name: reward-service + port: + number: 80 +``` + +### ConfigMap + +```yaml +# k8s/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: reward-service-config + namespace: rwadurian +data: + redis-host: "redis-master.rwadurian.svc.cluster.local" + redis-port: "6379" + kafka-brokers: "kafka-0.kafka.rwadurian.svc.cluster.local:9092" +``` + +### Secret + +```yaml +# k8s/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: reward-service-secrets + namespace: rwadurian +type: Opaque +stringData: + database-url: "postgresql://user:password@postgres:5432/reward_db" + jwt-secret: "your-jwt-secret-key" +``` + +### HorizontalPodAutoscaler + +```yaml +# k8s/hpa.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: reward-service-hpa + namespace: rwadurian +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: reward-service + minReplicas: 3 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 +``` + +### ้ƒจ็ฝฒๅ‘ฝไปค + +```bash +# ๅˆ›ๅปบๅ‘ฝๅ็ฉบ้—ด +kubectl create namespace rwadurian + +# ๅบ”็”จ้…็ฝฎ +kubectl apply -f k8s/configmap.yaml +kubectl apply -f k8s/secret.yaml + +# ้ƒจ็ฝฒๆœๅŠก +kubectl apply -f k8s/deployment.yaml +kubectl apply -f k8s/service.yaml +kubectl apply -f k8s/ingress.yaml +kubectl apply -f k8s/hpa.yaml + +# ๆŸฅ็œ‹้ƒจ็ฝฒ็Šถๆ€ +kubectl get pods -n rwadurian +kubectl get services -n rwadurian + +# ๆŸฅ็œ‹ๆ—ฅๅฟ— +kubectl logs -f deployment/reward-service -n rwadurian +``` + +--- + +## ๆ•ฐๆฎๅบ“่ฟ็งป + +### ็”Ÿไบง็Žฏๅขƒ่ฟ็งป + +```bash +# 1. ๅค‡ไปฝๆ•ฐๆฎๅบ“ +pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > backup_$(date +%Y%m%d).sql + +# 2. ่ฟ่กŒ่ฟ็งป +DATABASE_URL=$PRODUCTION_DATABASE_URL npx prisma migrate deploy + +# 3. ้ชŒ่ฏ่ฟ็งป +npx prisma db pull --print +``` + +### ๅ›žๆปš็ญ–็•ฅ + +```bash +# ๆŸฅ็œ‹่ฟ็งปๅކๅฒ +npx prisma migrate status + +# ๅ›žๆปšๅˆฐๆŒ‡ๅฎš็‰ˆๆœฌ (ๆ‰‹ๅŠจ) +psql -h $DB_HOST -U $DB_USER -d $DB_NAME < rollback_migration.sql +``` + +--- + +## ็Žฏๅขƒๅ˜้‡้…็ฝฎ + +### ็”Ÿไบง็Žฏๅขƒๅ˜้‡ + +| ๅ˜้‡ | ๆ่ฟฐ | ็คบไพ‹ | +|------|------|------| +| `NODE_ENV` | ่ฟ่กŒ็Žฏๅขƒ | `production` | +| `PORT` | ๆœๅŠก็ซฏๅฃ | `3000` | +| `DATABASE_URL` | ๆ•ฐๆฎๅบ“่ฟžๆŽฅไธฒ | `postgresql://user:pass@host:5432/db` | +| `REDIS_HOST` | Redisไธปๆœบ | `redis-master` | +| `REDIS_PORT` | Redis็ซฏๅฃ | `6379` | +| `KAFKA_BROKERS` | Kafka้›†็พค | `kafka-0:9092,kafka-1:9092` | +| `KAFKA_CLIENT_ID` | Kafkaๅฎขๆˆท็ซฏID | `reward-service` | +| `KAFKA_GROUP_ID` | Kafkaๆถˆ่ดน็ป„ID | `reward-service-group` | +| `JWT_SECRET` | JWTๅฏ†้’ฅ | `` | +| `LOG_LEVEL` | ๆ—ฅๅฟ—็บงๅˆซ | `info` | + +--- + +## ็›‘ๆŽงไธŽๅ‘Š่ญฆ + +### ๅฅๅบทๆฃ€ๆŸฅ็ซฏ็‚น + +```http +GET /health +``` + +ๅ“ๅบ”: +```json +{ + "status": "ok", + "service": "reward-service", + "timestamp": "2024-12-01T00:00:00.000Z" +} +``` + +### Prometheus ๆŒ‡ๆ ‡ + +ๆทปๅŠ  `@nestjs/terminus` ๅ’Œ Prometheus ๆŒ‡ๆ ‡๏ผš + +```typescript +// src/api/controllers/metrics.controller.ts +@Controller('metrics') +export class MetricsController { + @Get() + @Header('Content-Type', 'text/plain') + async getMetrics() { + return register.metrics(); + } +} +``` + +### ๅ…ณ้”ฎๆŒ‡ๆ ‡ + +| ๆŒ‡ๆ ‡ | ๆ่ฟฐ | ๅ‘Š่ญฆ้˜ˆๅ€ผ | +|------|------|---------| +| `http_request_duration_seconds` | ่ฏทๆฑ‚ๅ“ๅบ”ๆ—ถ้—ด | P99 > 2s | +| `http_requests_total` | ่ฏทๆฑ‚ๆ€ปๆ•ฐ | - | +| `http_request_errors_total` | ้”™่ฏฏ่ฏทๆฑ‚ๆ•ฐ | ้”™่ฏฏ็އ > 1% | +| `reward_distributed_total` | ๅˆ†้…็š„ๅฅ–ๅŠฑๆ•ฐ | - | +| `reward_settled_total` | ็ป“็ฎ—็š„ๅฅ–ๅŠฑๆ•ฐ | - | +| `reward_expired_total` | ่ฟ‡ๆœŸ็š„ๅฅ–ๅŠฑๆ•ฐ | - | + +### Grafana ไปช่กจๆฟ + +ๅ…ณ้”ฎ้ขๆฟ: +1. ่ฏทๆฑ‚ๅžๅ้‡ (QPS) +2. ๅ“ๅบ”ๆ—ถ้—ดๅˆ†ๅธƒ (P50/P90/P99) +3. ้”™่ฏฏ็އ +4. ๅฅ–ๅŠฑๅˆ†้…/็ป“็ฎ—/่ฟ‡ๆœŸ่ถ‹ๅŠฟ +5. ๆ•ฐๆฎๅบ“่ฟžๆŽฅๆฑ ็Šถๆ€ +6. Redis ็ผ“ๅญ˜ๅ‘ฝไธญ็އ + +--- + +## ๆ—ฅๅฟ—็ฎก็† + +### ๆ—ฅๅฟ—ๆ ผๅผ + +```typescript +// ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ—่พ“ๅ‡บ +{ + "timestamp": "2024-12-01T00:00:00.000Z", + "level": "info", + "context": "RewardApplicationService", + "message": "Distributed 6 rewards for order 123", + "metadata": { + "orderId": "123", + "userId": "100", + "rewardCount": 6 + } +} +``` + +### ๆ—ฅๅฟ—็บงๅˆซ + +| ็บงๅˆซ | ็”จ้€” | +|------|------| +| `error` | ้”™่ฏฏๅ’Œๅผ‚ๅธธ | +| `warn` | ่ญฆๅ‘Šไฟกๆฏ | +| `info` | ไธšๅŠกๆ—ฅๅฟ— | +| `debug` | ่ฐƒ่ฏ•ไฟกๆฏ (ไป…ๅผ€ๅ‘็Žฏๅขƒ) | + +### ELK ้›†ๆˆ + +```yaml +# filebeat.yml +filebeat.inputs: + - type: container + paths: + - /var/lib/docker/containers/*/*.log + processors: + - add_kubernetes_metadata: + +output.elasticsearch: + hosts: ["elasticsearch:9200"] + indices: + - index: "reward-service-%{+yyyy.MM.dd}" +``` + +--- + +## CI/CD ๆตๆฐด็บฟ + +### GitHub Actions + +```yaml +# .github/workflows/deploy.yml +name: Deploy to Production + +on: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci + - run: npm run lint + - run: npm test + + build: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build Docker image + run: docker build -t reward-service:${{ github.sha }} . + - name: Push to registry + run: | + docker tag reward-service:${{ github.sha }} ${{ secrets.REGISTRY }}/reward-service:${{ github.sha }} + docker push ${{ secrets.REGISTRY }}/reward-service:${{ github.sha }} + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to Kubernetes + run: | + kubectl set image deployment/reward-service \ + reward-service=${{ secrets.REGISTRY }}/reward-service:${{ github.sha }} \ + -n rwadurian +``` + +--- + +## ๆ•…้šœๆŽ’้™ค + +### ๅธธ่ง้—ฎ้ข˜ + +#### 1. ๆœๅŠกๆ— ๆณ•ๅฏๅŠจ + +```bash +# ๆฃ€ๆŸฅๆ—ฅๅฟ— +kubectl logs -f deployment/reward-service -n rwadurian + +# ๆฃ€ๆŸฅ็Žฏๅขƒๅ˜้‡ +kubectl exec -it deployment/reward-service -n rwadurian -- env + +# ๆฃ€ๆŸฅๆ•ฐๆฎๅบ“่ฟžๆŽฅ +kubectl exec -it deployment/reward-service -n rwadurian -- \ + npx prisma db pull +``` + +#### 2. ๆ•ฐๆฎๅบ“่ฟžๆŽฅ้—ฎ้ข˜ + +```bash +# ๆต‹่ฏ•ๆ•ฐๆฎๅบ“่ฟžๆŽฅ +kubectl run -it --rm debug --image=postgres:15-alpine --restart=Never -- \ + psql -h postgres -U user -d reward_db + +# ๆฃ€ๆŸฅ็ฝ‘็ปœ็ญ–็•ฅ +kubectl get networkpolicy -n rwadurian +``` + +#### 3. Kafka ่ฟžๆŽฅ้—ฎ้ข˜ + +```bash +# ๅˆ—ๅ‡บ Kafka topics +kubectl exec -it kafka-0 -- \ + kafka-topics --list --bootstrap-server localhost:9092 + +# ๆฃ€ๆŸฅๆถˆ่ดน่€…็ป„ +kubectl exec -it kafka-0 -- \ + kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group reward-service-group +``` + +### ๅ›žๆปš้ƒจ็ฝฒ + +```bash +# ๆŸฅ็œ‹ๅކๅฒ็‰ˆๆœฌ +kubectl rollout history deployment/reward-service -n rwadurian + +# ๅ›žๆปšๅˆฐไธŠไธ€็‰ˆๆœฌ +kubectl rollout undo deployment/reward-service -n rwadurian + +# ๅ›žๆปšๅˆฐๆŒ‡ๅฎš็‰ˆๆœฌ +kubectl rollout undo deployment/reward-service -n rwadurian --to-revision=2 +``` + +--- + +## ๅฎ‰ๅ…จๆœ€ไฝณๅฎž่ทต + +1. **ๅฏ†้’ฅ็ฎก็†**: ไฝฟ็”จ Kubernetes Secrets ๆˆ–ๅค–้ƒจๅฏ†้’ฅ็ฎก็†ๆœๅŠก (Vault) +2. **็ฝ‘็ปœ้š”็ฆป**: ไฝฟ็”จ NetworkPolicy ้™ๅˆถ Pod ้—ด้€šไฟก +3. **้•œๅƒๅฎ‰ๅ…จ**: ๅฎšๆœŸๆ‰ซๆ้•œๅƒๆผๆดž +4. **ๆœ€ๅฐๆƒ้™**: ไฝฟ็”จ้ž root ็”จๆˆท่ฟ่กŒๅฎนๅ™จ +5. **TLS**: ๅฏ็”จๆœๅŠก้—ด mTLS +6. **ๅฎก่ฎกๆ—ฅๅฟ—**: ่ฎฐๅฝ•ๆ‰€ๆœ‰ๆ•ๆ„Ÿๆ“ไฝœ diff --git a/backend/services/reward-service/docs/DEVELOPMENT.md b/backend/services/reward-service/docs/DEVELOPMENT.md new file mode 100644 index 00000000..65b6271c --- /dev/null +++ b/backend/services/reward-service/docs/DEVELOPMENT.md @@ -0,0 +1,544 @@ +# Reward Service ๅผ€ๅ‘ๆŒ‡ๅ— + +## ็Žฏๅขƒๅ‡†ๅค‡ + +### ็ณป็ปŸ่ฆๆฑ‚ + +- **Node.js**: >= 20.x LTS +- **npm**: >= 10.x +- **Docker**: >= 24.x (็”จไบŽๆœฌๅœฐๅผ€ๅ‘็Žฏๅขƒ) +- **Git**: >= 2.x + +### ๅผ€ๅ‘็Žฏๅขƒ่ฎพ็ฝฎ + +#### 1. ๅ…‹้š†้กน็›ฎ + +```bash +git clone +cd backend/services/reward-service +``` + +#### 2. ๅฎ‰่ฃ…ไพ่ต– + +```bash +npm install +``` + +#### 3. ้…็ฝฎ็Žฏๅขƒๅ˜้‡ + +ๅˆ›ๅปบ `.env` ๆ–‡ไปถ๏ผš + +```bash +cp .env.example .env +``` + +็ผ–่พ‘ `.env` ๆ–‡ไปถ๏ผš + +```env +# ๅบ”็”จ้…็ฝฎ +NODE_ENV=development +PORT=3000 + +# ๆ•ฐๆฎๅบ“้…็ฝฎ +DATABASE_URL="postgresql://postgres:password@localhost:5432/reward_db" + +# Redis้…็ฝฎ +REDIS_HOST=localhost +REDIS_PORT=6379 + +# Kafka้…็ฝฎ +KAFKA_BROKERS=localhost:9092 +KAFKA_CLIENT_ID=reward-service +KAFKA_GROUP_ID=reward-service-group + +# JWT้…็ฝฎ +JWT_SECRET=your-jwt-secret-key + +# ๅค–้ƒจๆœๅŠก้…็ฝฎ +REFERRAL_SERVICE_URL=http://localhost:3001 +AUTHORIZATION_SERVICE_URL=http://localhost:3002 +WALLET_SERVICE_URL=http://localhost:3003 +``` + +#### 4. ๅฏๅŠจๅŸบ็ก€่ฎพๆ–ฝ + +ไฝฟ็”จ Docker Compose ๅฏๅŠจ PostgreSQLใ€Redis ๅ’Œ Kafka๏ผš + +```bash +docker compose -f docker-compose.test.yml up -d +``` + +#### 5. ๆ•ฐๆฎๅบ“่ฟ็งป + +```bash +# ็”Ÿๆˆ Prisma Client +npx prisma generate + +# ่ฟ่กŒๆ•ฐๆฎๅบ“่ฟ็งป +npx prisma migrate dev +``` + +#### 6. ๅฏๅŠจๅผ€ๅ‘ๆœๅŠกๅ™จ + +```bash +# ๅผ€ๅ‘ๆจกๅผ (็ƒญ้‡่ฝฝ) +npm run start:dev + +# ่ฐƒ่ฏ•ๆจกๅผ +npm run start:debug +``` + +--- + +## ้กน็›ฎ็ป“ๆž„ + +``` +reward-service/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ api/ # APIๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ controllers/ # ๆŽงๅˆถๅ™จ +โ”‚ โ”‚ โ”œโ”€โ”€ dto/ # ๆ•ฐๆฎไผ ่พ“ๅฏน่ฑก +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ request/ # ่ฏทๆฑ‚DTO +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ response/ # ๅ“ๅบ”DTO +โ”‚ โ”‚ โ””โ”€โ”€ api.module.ts +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ application/ # ๅบ”็”จๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ services/ # ๅบ”็”จๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ schedulers/ # ๅฎšๆ—ถไปปๅŠก +โ”‚ โ”‚ โ””โ”€โ”€ application.module.ts +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ domain/ # ้ข†ๅŸŸๅฑ‚ (ๆ ธๅฟƒ) +โ”‚ โ”‚ โ”œโ”€โ”€ aggregates/ # ่šๅˆๆ น +โ”‚ โ”‚ โ”œโ”€โ”€ value-objects/ # ๅ€ผๅฏน่ฑก +โ”‚ โ”‚ โ”œโ”€โ”€ events/ # ้ข†ๅŸŸไบ‹ไปถ +โ”‚ โ”‚ โ”œโ”€โ”€ services/ # ้ข†ๅŸŸๆœๅŠก +โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ # ไป“ๅ‚จๆŽฅๅฃ +โ”‚ โ”‚ โ””โ”€โ”€ domain.module.ts +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ infrastructure/ # ๅŸบ็ก€่ฎพๆ–ฝๅฑ‚ +โ”‚ โ”‚ โ”œโ”€โ”€ persistence/ # ๆŒไน…ๅŒ– +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ prisma/ # Prisma้…็ฝฎ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ repositories/ # ไป“ๅ‚จๅฎž็Žฐ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ mappers/ # ๅฏน่ฑกๆ˜ ๅฐ„ +โ”‚ โ”‚ โ”œโ”€โ”€ external/ # ๅค–้ƒจๆœๅŠกๅฎขๆˆท็ซฏ +โ”‚ โ”‚ โ”œโ”€โ”€ kafka/ # Kafka้›†ๆˆ +โ”‚ โ”‚ โ”œโ”€โ”€ redis/ # Redis้›†ๆˆ +โ”‚ โ”‚ โ””โ”€โ”€ infrastructure.module.ts +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ shared/ # ๅ…ฑไบซๆจกๅ— +โ”‚ โ”‚ โ”œโ”€โ”€ guards/ # ๅฎˆๅซ +โ”‚ โ”‚ โ””โ”€โ”€ strategies/ # ่ฎค่ฏ็ญ–็•ฅ +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ config/ # ้…็ฝฎ +โ”‚ โ”œโ”€โ”€ app.module.ts # ๆ นๆจกๅ— +โ”‚ โ””โ”€โ”€ main.ts # ๅ…ฅๅฃๆ–‡ไปถ +โ”‚ +โ”œโ”€โ”€ test/ # ๆต‹่ฏ• +โ”‚ โ”œโ”€โ”€ integration/ # ้›†ๆˆๆต‹่ฏ• +โ”‚ โ””โ”€โ”€ app.e2e-spec.ts # E2Eๆต‹่ฏ• +โ”‚ +โ”œโ”€โ”€ prisma/ # Prisma้…็ฝฎ +โ”‚ โ””โ”€โ”€ schema.prisma +โ”‚ +โ”œโ”€โ”€ docs/ # ๆ–‡ๆกฃ +โ”œโ”€โ”€ Makefile # Makeๅ‘ฝไปค +โ””โ”€โ”€ docker-compose.test.yml # Docker้…็ฝฎ +``` + +--- + +## ๅผ€ๅ‘่ง„่Œƒ + +### ไปฃ็ ้ฃŽๆ ผ + +้กน็›ฎไฝฟ็”จ ESLint ๅ’Œ Prettier ่ฟ›่กŒไปฃ็ ่ง„่Œƒๆฃ€ๆŸฅ๏ผš + +```bash +# ่ฟ่กŒ ESLint ๆฃ€ๆŸฅๅนถ่‡ชๅŠจไฟฎๅค +npm run lint + +# ่ฟ่กŒ Prettier ๆ ผๅผๅŒ– +npm run format +``` + +### ๅ‘ฝๅ่ง„่Œƒ + +| ็ฑปๅž‹ | ่ง„่Œƒ | ็คบไพ‹ | +|------|------|------| +| ๆ–‡ไปถๅ | kebab-case | `reward-ledger-entry.aggregate.ts` | +| ็ฑปๅ | PascalCase | `RewardLedgerEntry` | +| ๆŽฅๅฃๅ | I + PascalCase | `IRewardLedgerEntryRepository` | +| ๆ–นๆณ•ๅ | camelCase | `calculateRewards` | +| ๅธธ้‡ | UPPER_SNAKE_CASE | `HEADQUARTERS_COMMUNITY_USER_ID` | +| ๆžšไธพๅ€ผ | UPPER_SNAKE_CASE | `SHARE_RIGHT` | + +### DDD ๅˆ†ๅฑ‚่ง„่Œƒ + +#### ้ข†ๅŸŸๅฑ‚ (Domain) + +้ข†ๅŸŸๅฑ‚ๆ˜ฏ็ณป็ปŸๆ ธๅฟƒ๏ผŒ**ไธไพ่ต–ไปปไฝ•ๅ…ถไป–ๅฑ‚**ใ€‚ + +```typescript +// โœ… ๆญฃ็กฎ๏ผš้ข†ๅŸŸๅฑ‚ๅชไฝฟ็”จ้ข†ๅŸŸๆฆ‚ๅฟต +import { Money } from '../value-objects/money.vo'; +import { RewardStatus } from '../value-objects/reward-status.enum'; + +// โŒ ้”™่ฏฏ๏ผš้ข†ๅŸŸๅฑ‚ไธๅบ”ไพ่ต–ๅŸบ็ก€่ฎพๆ–ฝ +import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; +``` + +#### ๅบ”็”จๅฑ‚ (Application) + +ๅบ”็”จๅฑ‚ๅ่ฐƒ้ข†ๅŸŸๅฑ‚ๅ’ŒๅŸบ็ก€่ฎพๆ–ฝๅฑ‚๏ผŒๅฎž็Žฐ็”จไพ‹ใ€‚ + +```typescript +@Injectable() +export class RewardApplicationService { + constructor( + // ๆณจๅ…ฅ้ข†ๅŸŸๆœๅŠก + private readonly rewardCalculationService: RewardCalculationService, + // ้€š่ฟ‡ๆŽฅๅฃๆณจๅ…ฅไป“ๅ‚จ + @Inject(REWARD_LEDGER_ENTRY_REPOSITORY) + private readonly repository: IRewardLedgerEntryRepository, + ) {} +} +``` + +#### ๅŸบ็ก€่ฎพๆ–ฝๅฑ‚ (Infrastructure) + +ๅŸบ็ก€่ฎพๆ–ฝๅฑ‚ๅฎž็Žฐ้ข†ๅŸŸๅฑ‚ๅฎšไน‰็š„ๆŽฅๅฃใ€‚ + +```typescript +// ไป“ๅ‚จๅฎž็Žฐ +@Injectable() +export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryRepository { + constructor(private readonly prisma: PrismaService) {} + + async save(entry: RewardLedgerEntry): Promise { + // ๅฎž็ŽฐๆŒไน…ๅŒ–้€ป่พ‘ + } +} +``` + +--- + +## ๆทปๅŠ ๆ–ฐๅŠŸ่ƒฝ + +### 1. ๆทปๅŠ ๆ–ฐ็š„ๅ€ผๅฏน่ฑก + +```typescript +// src/domain/value-objects/new-value.vo.ts +export class NewValue { + private readonly _value: number; + + private constructor(value: number) { + if (value < 0) { + throw new Error('Value must be non-negative'); + } + this._value = value; + } + + static create(value: number): NewValue { + return new NewValue(value); + } + + get value(): number { + return this._value; + } + + equals(other: NewValue): boolean { + return this._value === other._value; + } +} +``` + +### 2. ๆทปๅŠ ๆ–ฐ็š„้ข†ๅŸŸไบ‹ไปถ + +```typescript +// src/domain/events/new.event.ts +import { DomainEvent } from './domain-event.base'; + +export class NewEvent extends DomainEvent { + constructor( + public readonly data: { + id: string; + userId: string; + // ... ๅ…ถไป–ๅญ—ๆฎต + }, + ) { + super('NewEvent'); + } +} +``` + +### 3. ๆทปๅŠ ๆ–ฐ็š„่šๅˆๆ นๆ–นๆณ• + +```typescript +// ๅœจ่šๅˆๆ นไธญๆทปๅŠ ๆ–ฐ่กŒไธบ +export class RewardLedgerEntry { + // ... ็Žฐๆœ‰ไปฃ็  + + /** + * ๆ–ฐ็š„้ข†ๅŸŸ่กŒไธบ + */ + newBehavior(): void { + // 1. ๆฃ€ๆŸฅไธๅ˜ๅผ + if (!this.canPerformNewBehavior()) { + throw new Error('Cannot perform behavior in current state'); + } + + // 2. ไฟฎๆ”น็Šถๆ€ + this._someField = newValue; + + // 3. ๅ‘ๅธƒ้ข†ๅŸŸไบ‹ไปถ + this._domainEvents.push(new NewEvent({ + id: this._id?.toString() || '', + userId: this._userId.toString(), + })); + } + + private canPerformNewBehavior(): boolean { + // ไธšๅŠก่ง„ๅˆ™ๆฃ€ๆŸฅ + return true; + } +} +``` + +### 4. ๆทปๅŠ ๆ–ฐ็š„ API ็ซฏ็‚น + +```typescript +// src/api/controllers/new.controller.ts +@ApiTags('New') +@Controller('new') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class NewController { + constructor(private readonly service: RewardApplicationService) {} + + @Get() + @ApiOperation({ summary: 'ๆ–ฐๆŽฅๅฃๆ่ฟฐ' }) + @ApiResponse({ status: 200, description: 'ๆˆๅŠŸ' }) + async newEndpoint(@Request() req) { + const userId = BigInt(req.user.sub); + return this.service.newMethod(userId); + } +} +``` + +--- + +## ไพ่ต–ๆณจๅ…ฅ + +### ๅฎšไน‰ไป“ๅ‚จๆŽฅๅฃ + +```typescript +// src/domain/repositories/new.repository.interface.ts +export interface INewRepository { + findById(id: bigint): Promise; + save(entity: Entity): Promise; +} + +export const NEW_REPOSITORY = Symbol('INewRepository'); +``` + +### ๅฎž็Žฐไป“ๅ‚จ + +```typescript +// src/infrastructure/persistence/repositories/new.repository.impl.ts +@Injectable() +export class NewRepositoryImpl implements INewRepository { + constructor(private readonly prisma: PrismaService) {} + + async findById(id: bigint): Promise { + const data = await this.prisma.entity.findUnique({ where: { id } }); + return data ? EntityMapper.toDomain(data) : null; + } + + async save(entity: Entity): Promise { + const data = EntityMapper.toPersistence(entity); + await this.prisma.entity.upsert({ + where: { id: data.id }, + create: data, + update: data, + }); + } +} +``` + +### ๆณจๅ†Œไพ่ต– + +```typescript +// src/infrastructure/infrastructure.module.ts +@Module({ + providers: [ + { + provide: NEW_REPOSITORY, + useClass: NewRepositoryImpl, + }, + ], + exports: [NEW_REPOSITORY], +}) +export class InfrastructureModule {} +``` + +--- + +## ๅธธ็”จๅ‘ฝไปค + +### ๅผ€ๅ‘ๅ‘ฝไปค + +```bash +# ๅฏๅŠจๅผ€ๅ‘ๆœๅŠกๅ™จ (็ƒญ้‡่ฝฝ) +npm run start:dev + +# ๅฏๅŠจ่ฐƒ่ฏ•ๆจกๅผ +npm run start:debug + +# ๆž„ๅปบ็”Ÿไบง็‰ˆๆœฌ +npm run build + +# ๅฏๅŠจ็”ŸไบงๆœๅŠกๅ™จ +npm run start:prod +``` + +### ๆ•ฐๆฎๅบ“ๅ‘ฝไปค + +```bash +# ็”Ÿๆˆ Prisma Client +npx prisma generate + +# ๅˆ›ๅปบๆ–ฐ่ฟ็งป +npx prisma migrate dev --name + +# ๅบ”็”จ่ฟ็งป +npx prisma migrate deploy + +# ้‡็ฝฎๆ•ฐๆฎๅบ“ +npx prisma migrate reset --force + +# ๆ‰“ๅผ€ Prisma Studio +npx prisma studio +``` + +### ไปฃ็ ่ดจ้‡ + +```bash +# ESLint ๆฃ€ๆŸฅ +npm run lint + +# ไปฃ็ ๆ ผๅผๅŒ– +npm run format +``` + +### ๆต‹่ฏ•ๅ‘ฝไปค + +```bash +# ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• +npm test + +# ่ฟ่กŒๅ•ๅ…ƒๆต‹่ฏ• +make test-unit + +# ่ฟ่กŒ้›†ๆˆๆต‹่ฏ• +make test-integration + +# ่ฟ่กŒ E2E ๆต‹่ฏ• +make test-e2e + +# ๆต‹่ฏ•่ฆ†็›–็އ +npm run test:cov +``` + +--- + +## ่ฐƒ่ฏ•ๆŠ€ๅทง + +### VS Code ่ฐƒ่ฏ•้…็ฝฎ + +ๅˆ›ๅปบ `.vscode/launch.json`๏ผš + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach NestJS", + "port": 9229, + "restart": true, + "skipFiles": ["/**"] + }, + { + "type": "node", + "request": "launch", + "name": "Debug Jest Tests", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": ["--runInBand", "--watchAll=false"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} +``` + +### ๆ—ฅๅฟ—่ฐƒ่ฏ• + +```typescript +import { Logger } from '@nestjs/common'; + +@Injectable() +export class SomeService { + private readonly logger = new Logger(SomeService.name); + + async someMethod() { + this.logger.log('Processing started'); + this.logger.debug('Debug information', { data }); + this.logger.warn('Warning message'); + this.logger.error('Error occurred', error.stack); + } +} +``` + +--- + +## ๅธธ่ง้—ฎ้ข˜ + +### Q: ๅฆ‚ไฝ•ๅค„็† BigInt ๅบๅˆ—ๅŒ–้—ฎ้ข˜? + +```typescript +// ๅœจ JSON ๅบๅˆ—ๅŒ–ๆ—ถ่ฝฌๆขไธบๅญ—็ฌฆไธฒ +return { + id: entity.id?.toString(), + userId: entity.userId.toString(), +}; +``` + +### Q: ๅฆ‚ไฝ•ๆทปๅŠ ๆ–ฐ็š„ๅค–้ƒจๆœๅŠกไพ่ต–? + +1. ๅœจ `src/domain/services/` ไธญๅฎšไน‰ๆŽฅๅฃ (้˜ฒ่…ๅฑ‚) +2. ๅœจ `src/infrastructure/external/` ไธญๅฎž็Žฐๅฎขๆˆท็ซฏ +3. ๅœจๆจกๅ—ไธญๆณจๅ†Œไพ่ต–ๆณจๅ…ฅ + +### Q: ๅฆ‚ไฝ•ๅค„็†ๆ•ฐๆฎๅบ“ไบ‹ๅŠก? + +```typescript +await this.prisma.$transaction(async (tx) => { + await tx.rewardLedgerEntry.create({ data: entry1 }); + await tx.rewardLedgerEntry.create({ data: entry2 }); + await tx.rewardSummary.update({ where: { userId }, data: summary }); +}); +``` + +### Q: ๅฆ‚ไฝ•ๆต‹่ฏ•็งๆœ‰ๆ–นๆณ•? + +ไธ่ฆ็›ดๆŽฅๆต‹่ฏ•็งๆœ‰ๆ–นๆณ•ใ€‚้€š่ฟ‡ๅ…ฌๅ…ฑๆŽฅๅฃๆต‹่ฏ•็งๆœ‰ๆ–นๆณ•็š„่กŒไธบ๏ผš + +```typescript +// โŒ ้”™่ฏฏ๏ผš็›ดๆŽฅๆต‹่ฏ•็งๆœ‰ๆ–นๆณ• +expect(service['privateMethod']()).toBe(expected); + +// โœ… ๆญฃ็กฎ๏ผš้€š่ฟ‡ๅ…ฌๅ…ฑๆŽฅๅฃๆต‹่ฏ• +const result = await service.publicMethod(); +expect(result).toMatchObject({ /* expected behavior */ }); +``` diff --git a/backend/services/reward-service/docs/TESTING.md b/backend/services/reward-service/docs/TESTING.md new file mode 100644 index 00000000..c7d550ac --- /dev/null +++ b/backend/services/reward-service/docs/TESTING.md @@ -0,0 +1,808 @@ +# Reward Service ๆต‹่ฏ•ๆŒ‡ๅ— + +## ๆต‹่ฏ•ๆฆ‚่ฟฐ + +ๆœฌๆœๅŠก้‡‡็”จๅˆ†ๅฑ‚ๆต‹่ฏ•็ญ–็•ฅ๏ผŒ็กฎไฟไปฃ็ ่ดจ้‡ๅ’ŒไธšๅŠก้€ป่พ‘ๆญฃ็กฎๆ€ง๏ผš + +| ๆต‹่ฏ•็ฑปๅž‹ | ็›ฎ็š„ | ๆต‹่ฏ•่Œƒๅ›ด | ไพ่ต– | +|---------|------|---------|------| +| ๅ•ๅ…ƒๆต‹่ฏ• | ๆต‹่ฏ•้ข†ๅŸŸ้€ป่พ‘ | ๅ€ผๅฏน่ฑกใ€่šๅˆๆ น | ๆ— ๅค–้ƒจไพ่ต– | +| ้›†ๆˆๆต‹่ฏ• | ๆต‹่ฏ•ๆœๅŠกๅฑ‚ | ๅบ”็”จๆœๅŠกใ€้ข†ๅŸŸๆœๅŠก | Mockไพ่ต– | +| E2Eๆต‹่ฏ• | ๆต‹่ฏ•ๅฎŒๆ•ดๆต็จ‹ | API็ซฏ็‚น | MockๆœๅŠก | + +### ๆŠ€ๆœฏๆ ˆ + +- **ๆต‹่ฏ•ๆก†ๆžถ**: Jest 30.x +- **HTTPๆต‹่ฏ•**: Supertest 7.x +- **Mockๅทฅๅ…ท**: Jestๅ†…็ฝฎMock +- **ๅฎนๅ™จๅŒ–**: Docker Compose + +--- + +## ๆต‹่ฏ•ๆžถๆž„ + +``` +test/ +โ”œโ”€โ”€ integration/ # ้›†ๆˆๆต‹่ฏ• +โ”‚ โ”œโ”€โ”€ reward-application.service.spec.ts +โ”‚ โ””โ”€โ”€ reward-calculation.service.spec.ts +โ”‚ +โ”œโ”€โ”€ app.e2e-spec.ts # E2Eๆต‹่ฏ• +โ”œโ”€โ”€ jest-e2e.json # E2Eๆต‹่ฏ•้…็ฝฎ +โ””โ”€โ”€ setup.ts # ๆต‹่ฏ•็Žฏๅขƒ่ฎพ็ฝฎ + +src/ +โ”œโ”€โ”€ domain/ +โ”‚ โ”œโ”€โ”€ aggregates/ +โ”‚ โ”‚ โ”œโ”€โ”€ reward-ledger-entry/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ reward-ledger-entry.spec.ts # ๅ•ๅ…ƒๆต‹่ฏ• +โ”‚ โ”‚ โ””โ”€โ”€ reward-summary/ +โ”‚ โ”‚ โ””โ”€โ”€ reward-summary.spec.ts # ๅ•ๅ…ƒๆต‹่ฏ• +โ”‚ โ””โ”€โ”€ value-objects/ +โ”‚ โ”œโ”€โ”€ money.spec.ts # ๅ•ๅ…ƒๆต‹่ฏ• +โ”‚ โ””โ”€โ”€ hashpower.spec.ts # ๅ•ๅ…ƒๆต‹่ฏ• +``` + +--- + +## ่ฟ่กŒๆต‹่ฏ• + +### ๅฟซ้€Ÿๅ‘ฝไปค + +```bash +# ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• +npm test + +# ่ฟ่กŒๅ•ๅ…ƒๆต‹่ฏ• +make test-unit + +# ่ฟ่กŒ้›†ๆˆๆต‹่ฏ• +make test-integration + +# ่ฟ่กŒE2Eๆต‹่ฏ• +make test-e2e + +# ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• (Docker็Žฏๅขƒ) +make test-docker-all + +# ๆต‹่ฏ•่ฆ†็›–็އ +npm run test:cov +``` + +### Makefile ๅ‘ฝไปค่ฏฆ่งฃ + +```makefile +# ๅ•ๅ…ƒๆต‹่ฏ• - ๆต‹่ฏ•้ข†ๅŸŸ้€ป่พ‘ๅ’Œๅ€ผๅฏน่ฑก +test-unit: + npm test -- --testPathPatterns='src/.*\.spec\.ts$' --verbose + +# ้›†ๆˆๆต‹่ฏ• - ๆต‹่ฏ•ๆœๅŠกๅฑ‚ๅ’Œไป“ๅ‚จ +test-integration: + npm test -- --testPathPatterns='test/integration/.*\.spec\.ts$' --verbose + +# ็ซฏๅˆฐ็ซฏๆต‹่ฏ• - ๆต‹่ฏ•ๅฎŒๆ•ดAPIๆต็จ‹ +test-e2e: + npm run test:e2e -- --verbose + +# Docker็Žฏๅขƒไธญ่ฟ่กŒๆ‰€ๆœ‰ๆต‹่ฏ• +test-docker-all: + docker-compose -f docker-compose.test.yml up -d + sleep 5 + npm test -- --testPathPatterns='src/.*\.spec\.ts$' --verbose || true + npm test -- --testPathPatterns='test/integration/.*\.spec\.ts$' --verbose || true + npm run test:e2e -- --verbose || true + docker-compose -f docker-compose.test.yml down -v +``` + +--- + +## ๅ•ๅ…ƒๆต‹่ฏ• + +ๅ•ๅ…ƒๆต‹่ฏ•้’ˆๅฏน้ข†ๅŸŸๅฑ‚็š„็บฏไธšๅŠก้€ป่พ‘๏ผŒไธไพ่ต–ๅค–้ƒจๆœๅŠกใ€‚ + +### ๆต‹่ฏ•ๅ€ผๅฏน่ฑก + +```typescript +// src/domain/value-objects/money.spec.ts +describe('Money', () => { + describe('USDT factory', () => { + it('should create Money with USDT currency', () => { + const money = Money.USDT(100); + expect(money.amount).toBe(100); + expect(money.currency).toBe('USDT'); + }); + }); + + describe('validation', () => { + it('should throw error for negative amount', () => { + expect(() => Money.USDT(-100)).toThrow('้‡‘้ขไธ่ƒฝไธบ่ดŸๆ•ฐ'); + }); + }); + + describe('add', () => { + it('should add two Money values', () => { + const money1 = Money.USDT(100); + const money2 = Money.USDT(50); + const result = money1.add(money2); + expect(result.amount).toBe(150); + }); + }); + + describe('subtract', () => { + it('should return zero when subtracting larger value', () => { + const money1 = Money.USDT(50); + const money2 = Money.USDT(100); + const result = money1.subtract(money2); + expect(result.amount).toBe(0); + }); + }); +}); +``` + +### ๆต‹่ฏ•่šๅˆๆ น + +```typescript +// src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.spec.ts +describe('RewardLedgerEntry', () => { + describe('createPending', () => { + it('should create a pending reward with 24h expiration', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createTestRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + memo: 'Test reward', + }); + + expect(entry.isPending).toBe(true); + expect(entry.expireAt).toBeDefined(); + expect(entry.getRemainingTimeMs()).toBeGreaterThan(0); + }); + }); + + describe('claim', () => { + it('should transition pending to settleable', () => { + const entry = createPendingEntry(); + entry.claim(); + + expect(entry.isSettleable).toBe(true); + expect(entry.claimedAt).toBeDefined(); + expect(entry.expireAt).toBeNull(); + }); + + it('should throw error when not pending', () => { + const entry = createSettleableEntry(); + expect(() => entry.claim()).toThrow('ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ้ข†ๅ–'); + }); + }); + + describe('expire', () => { + it('should transition pending to expired', () => { + const entry = createPendingEntry(); + entry.expire(); + + expect(entry.isExpired).toBe(true); + expect(entry.expiredAt).toBeDefined(); + }); + }); + + describe('settle', () => { + it('should transition settleable to settled', () => { + const entry = createSettleableEntry(); + entry.settle('BNB', 0.25); + + expect(entry.isSettled).toBe(true); + expect(entry.settledAt).toBeDefined(); + }); + + it('should throw error when not settleable', () => { + const entry = createPendingEntry(); + expect(() => entry.settle('BNB', 0.25)).toThrow('ๅชๆœ‰ๅฏ็ป“็ฎ—็Šถๆ€ๆ‰่ƒฝ็ป“็ฎ—'); + }); + }); +}); +``` + +--- + +## ้›†ๆˆๆต‹่ฏ• + +้›†ๆˆๆต‹่ฏ•้ชŒ่ฏๅบ”็”จๆœๅŠกๅฑ‚ไธŽ้ข†ๅŸŸๆœๅŠก็š„ๅไฝœ๏ผŒไฝฟ็”จMock้š”็ฆปๅค–้ƒจไพ่ต–ใ€‚ + +### ๆต‹่ฏ•ๅบ”็”จๆœๅŠก + +```typescript +// test/integration/reward-application.service.spec.ts +describe('RewardApplicationService (Integration)', () => { + let service: RewardApplicationService; + let mockLedgerRepository: jest.Mocked; + let mockSummaryRepository: jest.Mocked; + let mockEventPublisher: jest.Mocked; + let mockWalletService: jest.Mocked; + + beforeEach(async () => { + // ๅˆ›ๅปบMockๅฏน่ฑก + mockLedgerRepository = { + save: jest.fn(), + saveAll: jest.fn(), + findByUserId: jest.fn(), + findPendingByUserId: jest.fn(), + findSettleableByUserId: jest.fn(), + findExpiredPending: jest.fn(), + countByUserId: jest.fn(), + }; + + mockSummaryRepository = { + findByUserId: jest.fn(), + getOrCreate: jest.fn(), + save: jest.fn(), + }; + + mockEventPublisher = { + publish: jest.fn(), + publishAll: jest.fn(), + }; + + mockWalletService = { + executeSwap: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + RewardApplicationService, + RewardCalculationService, + RewardExpirationService, + { + provide: REWARD_LEDGER_ENTRY_REPOSITORY, + useValue: mockLedgerRepository, + }, + { + provide: REWARD_SUMMARY_REPOSITORY, + useValue: mockSummaryRepository, + }, + { + provide: EventPublisherService, + useValue: mockEventPublisher, + }, + { + provide: WalletServiceClient, + useValue: mockWalletService, + }, + // ... ๅ…ถไป–Mock + ], + }).compile(); + + service = module.get(RewardApplicationService); + }); + + describe('distributeRewards', () => { + it('should distribute rewards and update summaries', async () => { + // Arrange + const params = { + sourceOrderId: BigInt(1), + sourceUserId: BigInt(100), + treeCount: 10, + provinceCode: '440000', + cityCode: '440100', + }; + + mockSummaryRepository.getOrCreate.mockResolvedValue( + RewardSummary.create(BigInt(100)) + ); + + // Act + await service.distributeRewards(params); + + // Assert + expect(mockLedgerRepository.saveAll).toHaveBeenCalled(); + expect(mockSummaryRepository.save).toHaveBeenCalled(); + expect(mockEventPublisher.publishAll).toHaveBeenCalled(); + }); + }); + + describe('settleRewards', () => { + it('should settle rewards and call wallet service', async () => { + // Arrange + const settleableRewards = [createSettleableEntry()]; + mockLedgerRepository.findSettleableByUserId.mockResolvedValue(settleableRewards); + mockSummaryRepository.getOrCreate.mockResolvedValue( + RewardSummary.create(BigInt(100)) + ); + mockWalletService.executeSwap.mockResolvedValue({ + success: true, + receivedAmount: 0.25, + txHash: '0x123', + }); + + // Act + const result = await service.settleRewards({ + userId: BigInt(100), + settleCurrency: 'BNB', + }); + + // Assert + expect(result.success).toBe(true); + expect(result.receivedAmount).toBe(0.25); + expect(mockWalletService.executeSwap).toHaveBeenCalledWith({ + userId: BigInt(100), + usdtAmount: expect.any(Number), + targetCurrency: 'BNB', + }); + }); + + it('should return error when no settleable rewards', async () => { + // Arrange + mockLedgerRepository.findSettleableByUserId.mockResolvedValue([]); + + // Act + const result = await service.settleRewards({ + userId: BigInt(100), + settleCurrency: 'BNB', + }); + + // Assert + expect(result.success).toBe(false); + expect(result.error).toBe('ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š'); + }); + }); +}); +``` + +### ๆต‹่ฏ•้ข†ๅŸŸๆœๅŠก + +```typescript +// test/integration/reward-calculation.service.spec.ts +describe('RewardCalculationService (Integration)', () => { + let service: RewardCalculationService; + let mockReferralService: jest.Mocked; + let mockAuthorizationService: jest.Mocked; + + beforeEach(async () => { + mockReferralService = { + getReferralChain: jest.fn(), + }; + + mockAuthorizationService = { + findNearestAuthorizedProvince: jest.fn(), + findNearestAuthorizedCity: jest.fn(), + findNearestCommunity: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + RewardCalculationService, + { + provide: REFERRAL_SERVICE_CLIENT, + useValue: mockReferralService, + }, + { + provide: AUTHORIZATION_SERVICE_CLIENT, + useValue: mockAuthorizationService, + }, + ], + }).compile(); + + service = module.get(RewardCalculationService); + }); + + describe('calculateRewards', () => { + const baseParams = { + sourceOrderId: BigInt(1), + sourceUserId: BigInt(100), + treeCount: 10, + provinceCode: '440000', + cityCode: '440100', + }; + + it('should calculate all 6 types of rewards', async () => { + // Arrange + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: true }], + }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300)); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400)); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500)); + + // Act + const rewards = await service.calculateRewards(baseParams); + + // Assert + expect(rewards).toHaveLength(6); + + const rightTypes = rewards.map(r => r.rewardSource.rightType); + expect(rightTypes).toContain(RightType.SHARE_RIGHT); + expect(rightTypes).toContain(RightType.PROVINCE_TEAM_RIGHT); + expect(rightTypes).toContain(RightType.PROVINCE_AREA_RIGHT); + expect(rightTypes).toContain(RightType.CITY_TEAM_RIGHT); + expect(rightTypes).toContain(RightType.CITY_AREA_RIGHT); + expect(rightTypes).toContain(RightType.COMMUNITY_RIGHT); + }); + + it('should calculate share right reward (500 USDT) when referrer has planted', async () => { + // Arrange + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: true }], + }); + // ... ๅ…ถไป–Mock่ฎพ็ฝฎ + + // Act + const rewards = await service.calculateRewards(baseParams); + + // Assert + const shareReward = rewards.find( + r => r.rewardSource.rightType === RightType.SHARE_RIGHT + ); + expect(shareReward).toBeDefined(); + expect(shareReward?.isSettleable).toBe(true); + expect(shareReward?.usdtAmount.amount).toBe(500 * 10); + }); + + it('should create pending share right reward when referrer has not planted', async () => { + // Arrange + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: false }], + }); + + // Act + const rewards = await service.calculateRewards(baseParams); + + // Assert + const shareReward = rewards.find( + r => r.rewardSource.rightType === RightType.SHARE_RIGHT + ); + expect(shareReward?.isPending).toBe(true); + }); + }); +}); +``` + +--- + +## E2Eๆต‹่ฏ• + +E2Eๆต‹่ฏ•้ชŒ่ฏๅฎŒๆ•ด็š„HTTP่ฏทๆฑ‚-ๅ“ๅบ”ๆต็จ‹ใ€‚ + +### ๆต‹่ฏ•้…็ฝฎ + +```json +// test/jest-e2e.json +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "moduleNameMapper": { + "^src/(.*)$": "/../src/$1" + } +} +``` + +### E2Eๆต‹่ฏ•็คบไพ‹ + +```typescript +// test/app.e2e-spec.ts +describe('Reward Service (e2e)', () => { + let app: INestApplication; + let jwtService: JwtService; + let mockRewardService: any; + + const TEST_JWT_SECRET = 'test-secret-key-for-testing'; + + const createTestToken = (userId: string = '100') => { + return jwtService.sign({ + sub: userId, + username: 'testuser', + roles: ['user'], + }); + }; + + beforeEach(async () => { + mockRewardService = { + getRewardSummary: jest.fn().mockResolvedValue({ + pendingUsdt: 1000, + pendingHashpower: 0.5, + pendingExpireAt: new Date(Date.now() + 12 * 60 * 60 * 1000), + settleableUsdt: 500, + settleableHashpower: 0.2, + settledTotalUsdt: 2000, + settledTotalHashpower: 1.0, + expiredTotalUsdt: 100, + expiredTotalHashpower: 0.1, + }), + // ... ๅ…ถไป–Mockๆ–นๆณ• + }; + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [() => ({ JWT_SECRET: TEST_JWT_SECRET })], + }), + PassportModule, + JwtModule.register({ + secret: TEST_JWT_SECRET, + signOptions: { expiresIn: '1h' }, + }), + ], + controllers: [HealthController, RewardController, SettlementController], + providers: [ + { + provide: RewardApplicationService, + useValue: mockRewardService, + }, + // ... JwtStrategy้…็ฝฎ + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes(new ValidationPipe({ transform: true })); + await app.init(); + + jwtService = moduleFixture.get(JwtService); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('Health Check', () => { + it('/health (GET) should return healthy status', () => { + return request(app.getHttpServer()) + .get('/health') + .expect(200) + .expect((res) => { + expect(res.body.status).toBe('ok'); + expect(res.body.service).toBe('reward-service'); + }); + }); + }); + + describe('Rewards API', () => { + describe('GET /rewards/summary', () => { + it('should return 401 without auth token', () => { + return request(app.getHttpServer()) + .get('/rewards/summary') + .expect(401); + }); + + it('should return reward summary with valid token', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .get('/rewards/summary') + .set('Authorization', `Bearer ${token}`) + .expect(200) + .expect((res) => { + expect(res.body.pendingUsdt).toBe(1000); + expect(res.body.settleableUsdt).toBe(500); + }); + }); + }); + }); + + describe('Settlement API', () => { + describe('POST /rewards/settle', () => { + it('should settle rewards successfully with valid token', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: 'BNB' }) + .expect(201) + .expect((res) => { + expect(res.body.success).toBe(true); + }); + }); + + it('should validate settleCurrency parameter', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: '' }) + .expect(400); + }); + }); + }); +}); +``` + +--- + +## Dockerๆต‹่ฏ•็Žฏๅขƒ + +### docker-compose.test.yml + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + container_name: reward-test-postgres + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: reward_test + ports: + - '5433:5432' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U test -d reward_test'] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + container_name: reward-test-redis + ports: + - '6380:6379' + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 5s + timeout: 5s + retries: 5 + + kafka: + image: confluentinc/cp-kafka:7.5.0 + container_name: reward-test-kafka + depends_on: + - zookeeper + ports: + - '9093:9092' + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + # ... ๅ…ถไป–้…็ฝฎ +``` + +### ่ฟ่กŒDockerๆต‹่ฏ• + +```bash +# ๅฏๅŠจๆต‹่ฏ•ไพ่ต– +make docker-up + +# ่ฟ่กŒๆต‹่ฏ• +make test-docker-all + +# ๅ…ณ้—ญๆต‹่ฏ•ไพ่ต– +make docker-down +``` + +--- + +## ๆต‹่ฏ•่ฆ†็›–็އ + +### ็”Ÿๆˆ่ฆ†็›–็އๆŠฅๅ‘Š + +```bash +npm run test:cov +``` + +### ่ฆ†็›–็އ็›ฎๆ ‡ + +| ๆŒ‡ๆ ‡ | ็›ฎๆ ‡ | +|------|------| +| ่ฏญๅฅ่ฆ†็›–็އ (Statements) | >= 80% | +| ๅˆ†ๆ”ฏ่ฆ†็›–็އ (Branches) | >= 75% | +| ๅ‡ฝๆ•ฐ่ฆ†็›–็އ (Functions) | >= 85% | +| ่กŒ่ฆ†็›–็އ (Lines) | >= 80% | + +### ๆŸฅ็œ‹ๆŠฅๅ‘Š + +่ฆ†็›–็އๆŠฅๅ‘Š็”Ÿๆˆๅœจ `coverage/` ็›ฎๅฝ•๏ผš + +```bash +# ๅœจๆต่งˆๅ™จไธญๆ‰“ๅผ€HTMLๆŠฅๅ‘Š +open coverage/lcov-report/index.html +``` + +--- + +## ๆต‹่ฏ•ๆœ€ไฝณๅฎž่ทต + +### 1. ๆต‹่ฏ•ๅ‘ฝๅ่ง„่Œƒ + +```typescript +// ไฝฟ็”จ describe ็ป„็ป‡ๆต‹่ฏ• +describe('RewardLedgerEntry', () => { + describe('claim', () => { + it('should transition pending to settleable', () => { ... }); + it('should throw error when not pending', () => { ... }); + }); +}); +``` + +### 2. AAA ๆจกๅผ + +```typescript +it('should calculate total amount', () => { + // Arrange - ๅ‡†ๅค‡ๆต‹่ฏ•ๆ•ฐๆฎ + const reward1 = createReward(100); + const reward2 = createReward(200); + + // Act - ๆ‰ง่กŒ่ขซๆต‹่ฏ•็š„่กŒไธบ + const total = service.calculateTotal([reward1, reward2]); + + // Assert - ้ชŒ่ฏ็ป“ๆžœ + expect(total).toBe(300); +}); +``` + +### 3. ไฝฟ็”จๅทฅๅŽ‚ๅ‡ฝๆ•ฐๅˆ›ๅปบๆต‹่ฏ•ๆ•ฐๆฎ + +```typescript +// test/helpers/factories.ts +export function createTestRewardSource(overrides = {}) { + return RewardSource.create( + RightType.SHARE_RIGHT, + BigInt(1), + BigInt(100), + ...overrides, + ); +} + +export function createPendingEntry(overrides = {}) { + return RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createTestRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + ...overrides, + }); +} +``` + +### 4. ้ฟๅ…ๆต‹่ฏ•ๅฎž็Žฐ็ป†่Š‚ + +```typescript +// โŒ ๆต‹่ฏ•ๅฎž็Žฐ็ป†่Š‚ +expect(service['privateField']).toBe(expectedValue); + +// โœ… ๆต‹่ฏ•่กŒไธบ +const result = await service.publicMethod(); +expect(result).toMatchObject({ status: 'success' }); +``` + +### 5. ไฝฟ็”จ Mock ้š”็ฆปไพ่ต– + +```typescript +// ๅˆ›ๅปบ็ฑปๅž‹ๅฎ‰ๅ…จ็š„Mock +const mockRepository = { + save: jest.fn(), + findById: jest.fn(), +} as jest.Mocked; + +// ่ฎพ็ฝฎMock่ฟ”ๅ›žๅ€ผ +mockRepository.findById.mockResolvedValue(expectedEntry); + +// ้ชŒ่ฏMock่ขซ่ฐƒ็”จ +expect(mockRepository.save).toHaveBeenCalledWith(expect.objectContaining({ + userId: BigInt(100), +})); +``` + +--- + +## ๆต‹่ฏ•ๆŠฅๅ‘Š็คบไพ‹ + +``` +Test Suites: 7 passed, 7 total +Tests: 77 passed, 77 total +Snapshots: 0 total +Time: 13.026 s + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๆต‹่ฏ•็ป“ๆžœๆฑ‡ๆ€ป โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๆต‹่ฏ•็ฑปๅž‹ โ”‚ ๆต‹่ฏ•ๅฅ—ไปถ โ”‚ ๆต‹่ฏ•็”จไพ‹ โ”‚ ็Šถๆ€ โ”‚ ่€—ๆ—ถ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๅ•ๅ…ƒๆต‹่ฏ• โ”‚ 4 โ”‚ 43 โ”‚ โœ… ้€š่ฟ‡ โ”‚ 3.2s โ”‚ +โ”‚ ้›†ๆˆๆต‹่ฏ• โ”‚ 2 โ”‚ 20 โ”‚ โœ… ้€š่ฟ‡ โ”‚ 4.8s โ”‚ +โ”‚ E2Eๆต‹่ฏ• โ”‚ 1 โ”‚ 14 โ”‚ โœ… ้€š่ฟ‡ โ”‚ 5.0s โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๆ€ป่ฎก โ”‚ 7 โ”‚ 77 โ”‚ โœ… ้€š่ฟ‡ โ”‚ ~13s โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` diff --git a/backend/services/reward-service/eslint.config.mjs b/backend/services/reward-service/eslint.config.mjs new file mode 100644 index 00000000..4e9f8271 --- /dev/null +++ b/backend/services/reward-service/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + "prettier/prettier": ["error", { endOfLine: "auto" }], + }, + }, +); diff --git a/backend/services/reward-service/nest-cli.json b/backend/services/reward-service/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/backend/services/reward-service/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/backend/services/reward-service/package-lock.json b/backend/services/reward-service/package-lock.json new file mode 100644 index 00000000..6ec4cfc8 --- /dev/null +++ b/backend/services/reward-service/package-lock.json @@ -0,0 +1,11412 @@ +{ + "name": "reward-service", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reward-service", + "version": "0.0.1", + "license": "UNLICENSED", + "dependencies": { + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.1", + "@nestjs/cqrs": "^11.0.3", + "@nestjs/jwt": "^11.0.1", + "@nestjs/microservices": "^11.1.9", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.0.1", + "@nestjs/schedule": "^6.0.1", + "@nestjs/swagger": "^11.2.3", + "@prisma/client": "^7.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.3", + "dotenv": "^17.2.3", + "ioredis": "^5.8.2", + "kafkajs": "^2.2.4", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.7", + "@types/passport-jwt": "^4.0.1", + "@types/supertest": "^6.0.2", + "@types/uuid": "^10.0.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^16.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "prisma": "^7.0.1", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", + "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "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 + } + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.19.tgz", + "integrity": "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.19", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "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" + } + }, + "node_modules/@angular-devkit/schematics-cli": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.19.tgz", + "integrity": "sha512-7q9UY6HK6sccL9F3cqGRUwKhM7b/XfD2YcVaZ2WD7VMaRlRm85v6mRjSrfKIAwxcQU0UK27kMc79NIIqaHjzxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", + "@inquirer/prompts": "7.3.2", + "ansi-colors": "4.1.3", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "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" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@electric-sql/pglite": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", + "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", + "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", + "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.2.tgz", + "integrity": "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "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" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@ioredis/commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", + "license": "MIT" + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.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": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "license": "MIT" + }, + "node_modules/@mrleebo/prisma-ast": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", + "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.5.0", + "lilconfig": "^2.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@nestjs/cli": { + "version": "11.0.14", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.14.tgz", + "integrity": "sha512-YwP03zb5VETTwelXU+AIzMVbEZKk/uxJL+z9pw0mdG9ogAtqZ6/mpmIM4nEq/NU8D0a7CBRLcMYUmWW/55pfqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", + "@angular-devkit/schematics-cli": "19.2.19", + "@inquirer/prompts": "7.10.1", + "@nestjs/schematics": "^11.0.1", + "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", + "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.103.0", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 20.11" + }, + "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 + } + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.9.tgz", + "integrity": "sha512-zDntUTReRbAThIfSp3dQZ9kKqI+LjgLp5YZN5c1bgNRDuoeLySAoZg46Bg1a+uV8TMgIRziHocglKGNzr6l+bQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "file-type": "21.1.0", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "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 + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.9.tgz", + "integrity": "sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nuxt/opencollective": "0.4.1", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "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 + } + } + }, + "node_modules/@nestjs/cqrs": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-11.0.3.tgz", + "integrity": "sha512-2ezBftiXqVfNTzjCrmhazohYhIQzgm8rvM0aKndv73IOOBcVlNuNiQ3HHiHdd4c2w/3MOQDtsGbQHgZUuW6DPw==", + "license": "MIT", + "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" + } + }, + "node_modules/@nestjs/jwt": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz", + "integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/@nestjs/microservices": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.1.9.tgz", + "integrity": "sha512-2n9ftdRej+vv+MMO863dDvxBaJ/VHkzroo/Nyl59mE23mCYno3NZEn0QgCYByktoE8r7Cc0kSOqrTVE++KALug==", + "license": "MIT", + "peer": true, + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@grpc/grpc-js": "*", + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "amqp-connection-manager": "*", + "amqplib": "*", + "cache-manager": "*", + "ioredis": "*", + "kafkajs": "*", + "mqtt": "*", + "nats": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@grpc/grpc-js": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + }, + "amqp-connection-manager": { + "optional": true + }, + "amqplib": { + "optional": true + }, + "cache-manager": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "kafkajs": { + "optional": true + }, + "mqtt": { + "optional": true + }, + "nats": { + "optional": true + } + } + }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/@nestjs/platform-express": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.9.tgz", + "integrity": "sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==", + "license": "MIT", + "peer": true, + "dependencies": { + "cors": "2.8.5", + "express": "5.1.0", + "multer": "2.0.2", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" + } + }, + "node_modules/@nestjs/schedule": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.1.tgz", + "integrity": "sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==", + "license": "MIT", + "dependencies": { + "cron": "4.3.3" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/schematics": { + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz", + "integrity": "sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.17", + "@angular-devkit/schematics": "19.2.17", + "comment-json": "4.4.1", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.17.tgz", + "integrity": "sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "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 + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.17.tgz", + "integrity": "sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.17", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "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" + } + }, + "node_modules/@nestjs/schematics/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/schematics/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@nestjs/swagger": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.3.tgz", + "integrity": "sha512-a0xFfjeqk69uHIUpP8u0ryn4cKuHdra2Ug96L858i0N200Hxho+n3j+TlQXyOF4EstLSGjTfxI1Xb2E1lUxeNg==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.21", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.30.2" + }, + "peerDependencies": { + "@fastify/static": "^8.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 + } + } + }, + "node_modules/@nestjs/testing": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.9.tgz", + "integrity": "sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "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 + } + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nuxt/opencollective": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", + "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0", + "npm": ">=5.10.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@prisma/client": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz", + "integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.0.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz", + "integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.0.1.tgz", + "integrity": "sha512-MacIjXdo+hNKxPvtMzDXykIIc8HCRWoyjQ2nguJTFqLDzJBD5L6QRaANGTLOqbGtJ3sFvLRmfXhrFg3pWoK1BA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz", + "integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.13.0.tgz", + "integrity": "sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.3.2", + "@electric-sql/pglite-socket": "0.0.6", + "@electric-sql/pglite-tools": "0.2.7", + "@hono/node-server": "1.14.2", + "@mrleebo/prisma-ast": "0.12.1", + "@prisma/get-platform": "6.8.2", + "@prisma/query-plan-executor": "6.18.0", + "foreground-child": "3.3.1", + "get-port-please": "3.1.2", + "hono": "4.7.10", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.21.3", + "std-env": "3.9.0", + "valibot": "1.1.0", + "zeptomatch": "2.0.2" + } + }, + "node_modules/@prisma/engines": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.0.1.tgz", + "integrity": "sha512-f+D/vdKeImqUHysd5Bgv8LQ1whl4sbLepHyYMQQMK61cp4WjwJVryophleLUrfEJRpBLGTBI/7fnLVENxxMFPQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/fetch-engine": "7.0.1", + "@prisma/get-platform": "7.0.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6.tgz", + "integrity": "sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.0.1.tgz", + "integrity": "sha512-5DnSairYIYU7dcv/9pb1KCwIRHZfhVOd34855d01lUI5QdF9rdCkMywPQbBM67YP7iCgQoEZO0/COtOMpR4i9A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/get-platform": "7.0.1" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", + "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.8.2" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", + "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", + "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/studio-core": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.8.2.tgz", + "integrity": "sha512-/iAEWEUpTja+7gVMu1LtR2pPlvDmveAwMHdTWbDeGlT7yiv0ZTCPpmeAGdq/Y9aJ9Zj1cEGBXGRbmmNPj022PQ==", + "devOptional": true, + "license": "UNLICENSED", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.3.1.tgz", + "integrity": "sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.1", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", + "license": "MIT" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", + "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/type-utils": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.48.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", + "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", + "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", + "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT", + "peer": true + }, + "node_modules/class-validator": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", + "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.20" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz", + "integrity": "sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cron": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz", + "integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==", + "license": "MIT", + "dependencies": { + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" + }, + "engines": { + "node": ">=18.x" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "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 + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-check/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.0.tgz", + "integrity": "sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.3.1", + "strtok3": "^10.3.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^4.0.1", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-port-please": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", + "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/grammex": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", + "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.10.tgz", + "integrity": "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", + "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ioredis/commands": "1.4.0", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "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.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.30", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.30.tgz", + "integrity": "sha512-KxH7uIJFD6+cR6nhdh+wY6prFiH26A3W/W1gTMXnng2PXSwVfi5MhYkdq3Z2Y7vhBVa1/5VJgpNtI76UM2njGA==", + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru.min": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", + "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz", + "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prisma": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.0.1.tgz", + "integrity": "sha512-zp93MdFMSU1IHPEXbUHVUuD8wauh2BUm14OVxhxGrWJQQpXpda0rW4VSST2bci4raoldX64/wQxHKkl/wqDskQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@prisma/config": "7.0.1", + "@prisma/dev": "0.13.0", + "@prisma/engines": "7.0.1", + "@prisma/studio-core": "0.8.2", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/remeda": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", + "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.39.1" + } + }, + "node_modules/remeda/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "devOptional": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.30.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz", + "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "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", + "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 + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "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 + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", + "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.48.0", + "@typescript-eslint/parser": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validator": { + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", + "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "dev": true, + "license": "MIT", + "peer": true, + "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.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.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.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zeptomatch": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", + "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.10" + } + } + } +} diff --git a/backend/services/reward-service/package.json b/backend/services/reward-service/package.json index e69de29b..6b208d59 100644 --- a/backend/services/reward-service/package.json +++ b/backend/services/reward-service/package.json @@ -0,0 +1,94 @@ +{ + "name": "reward-service", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "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.json" + }, + "dependencies": { + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.1", + "@nestjs/cqrs": "^11.0.3", + "@nestjs/jwt": "^11.0.1", + "@nestjs/microservices": "^11.1.9", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.0.1", + "@nestjs/schedule": "^6.0.1", + "@nestjs/swagger": "^11.2.3", + "@prisma/client": "^7.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.3", + "dotenv": "^17.2.3", + "ioredis": "^5.8.2", + "kafkajs": "^2.2.4", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@types/express": "^5.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.10.7", + "@types/passport-jwt": "^4.0.1", + "@types/supertest": "^6.0.2", + "@types/uuid": "^10.0.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^16.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "prisma": "^7.0.1", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": ".", + "roots": ["/src", "/test"], + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "src/**/*.(t|j)s" + ], + "coverageDirectory": "./coverage", + "testEnvironment": "node", + "moduleNameMapper": { + "^src/(.*)$": "/src/$1" + } + } +} diff --git a/backend/services/reward-service/prisma.config.ts b/backend/services/reward-service/prisma.config.ts new file mode 100644 index 00000000..9c5e9593 --- /dev/null +++ b/backend/services/reward-service/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: env("DATABASE_URL"), + }, +}); diff --git a/backend/services/reward-service/prisma/schema.prisma b/backend/services/reward-service/prisma/schema.prisma new file mode 100644 index 00000000..ca31cb9c --- /dev/null +++ b/backend/services/reward-service/prisma/schema.prisma @@ -0,0 +1,175 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" +} + +// ============================================ +// ๅฅ–ๅŠฑๆตๆฐด่กจ (่šๅˆๆ น1 - ่กŒไธบ่กจ, append-only) +// ่ฎฐๅฝ•ๆฏไธ€็ฌ”ๅฅ–ๅŠฑ็š„ๅˆ›ๅปบใ€้ข†ๅ–ใ€็ป“็ฎ—ใ€่ฟ‡ๆœŸ +// ============================================ +model RewardLedgerEntry { + id BigInt @id @default(autoincrement()) @map("entry_id") + userId BigInt @map("user_id") // ๆŽฅๆ”ถๅฅ–ๅŠฑ็š„็”จๆˆทID + + // === ๅฅ–ๅŠฑๆฅๆบ === + sourceOrderId BigInt @map("source_order_id") // ๆฅๆบ่ฎค็ง่ฎขๅ•ID + sourceUserId BigInt @map("source_user_id") // ่งฆๅ‘ๅฅ–ๅŠฑ็š„็”จๆˆทID(่ฎค็ง่€…) + rightType String @map("right_type") @db.VarChar(50) // ๆƒ็›Š็ฑปๅž‹ + + // === ๅฅ–ๅŠฑ้‡‘้ข === + usdtAmount Decimal @map("usdt_amount") @db.Decimal(20, 8) + hashpowerAmount Decimal @default(0) @map("hashpower_amount") @db.Decimal(20, 8) + + // === ๅฅ–ๅŠฑ็Šถๆ€ === + rewardStatus String @default("PENDING") @map("reward_status") @db.VarChar(20) + + // === ๆ—ถ้—ดๆˆณ === + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6) + expireAt DateTime? @map("expire_at") // ่ฟ‡ๆœŸๆ—ถ้—ด(24hๅŽ) + claimedAt DateTime? @map("claimed_at") // ้ข†ๅ–ๆ—ถ้—ด(็”จๆˆท่ฎค็ง) + settledAt DateTime? @map("settled_at") // ็ป“็ฎ—ๆ—ถ้—ด + expiredAt DateTime? @map("expired_at") // ๅฎž้™…่ฟ‡ๆœŸๆ—ถ้—ด + + // === ๅค‡ๆณจ === + memo String? @map("memo") @db.VarChar(500) + + @@map("reward_ledger_entries") + @@index([userId, rewardStatus], name: "idx_user_status") + @@index([userId, createdAt(sort: Desc)], name: "idx_user_created") + @@index([sourceOrderId], name: "idx_source_order") + @@index([sourceUserId], name: "idx_source_user") + @@index([rightType], name: "idx_right_type") + @@index([rewardStatus], name: "idx_status") + @@index([expireAt], name: "idx_expire") + @@index([createdAt], name: "idx_created") +} + +// ============================================ +// ๅฅ–ๅŠฑๆฑ‡ๆ€ป่กจ (่šๅˆๆ น2 - ็Šถๆ€่กจ) +// ๆฏไธช็”จๆˆท็š„ๆ”ถ็›Šๆฑ‡ๆ€ป๏ผŒไปŽๆตๆฐด่กจ่šๅˆ +// ============================================ +model RewardSummary { + id BigInt @id @default(autoincrement()) @map("summary_id") + userId BigInt @unique @map("user_id") + + // === ๅพ…้ข†ๅ–ๆ”ถ็›Š (24hๅ€’่ฎกๆ—ถ) === + pendingUsdt Decimal @default(0) @map("pending_usdt") @db.Decimal(20, 8) + pendingHashpower Decimal @default(0) @map("pending_hashpower") @db.Decimal(20, 8) + pendingExpireAt DateTime? @map("pending_expire_at") // ๆœ€ๆ—ฉ่ฟ‡ๆœŸๆ—ถ้—ด + + // === ๅฏ็ป“็ฎ—ๆ”ถ็›Š === + settleableUsdt Decimal @default(0) @map("settleable_usdt") @db.Decimal(20, 8) + settleableHashpower Decimal @default(0) @map("settleable_hashpower") @db.Decimal(20, 8) + + // === ๅทฒ็ป“็ฎ—ๆ”ถ็›Š (็ดฏ่ฎก) === + settledTotalUsdt Decimal @default(0) @map("settled_total_usdt") @db.Decimal(20, 8) + settledTotalHashpower Decimal @default(0) @map("settled_total_hashpower") @db.Decimal(20, 8) + + // === ๅทฒ่ฟ‡ๆœŸๆ”ถ็›Š (็ดฏ่ฎก) === + expiredTotalUsdt Decimal @default(0) @map("expired_total_usdt") @db.Decimal(20, 8) + expiredTotalHashpower Decimal @default(0) @map("expired_total_hashpower") @db.Decimal(20, 8) + + // === ๆ—ถ้—ดๆˆณ === + lastUpdateAt DateTime @default(now()) @updatedAt @map("last_update_at") + createdAt DateTime @default(now()) @map("created_at") + + @@map("reward_summaries") + @@index([userId], name: "idx_summary_user") + @@index([settleableUsdt(sort: Desc)], name: "idx_settleable_desc") + @@index([pendingExpireAt], name: "idx_pending_expire") +} + +// ============================================ +// ๆƒ็›Šๅฎšไน‰่กจ (้…็ฝฎ่กจ) +// ๅฎšไน‰ๆฏ็งๆƒ็›Š็š„ๅฅ–ๅŠฑ่ง„ๅˆ™ +// ============================================ +model RightDefinition { + id BigInt @id @default(autoincrement()) @map("definition_id") + rightType String @unique @map("right_type") @db.VarChar(50) + + // === ๅฅ–ๅŠฑ่ง„ๅˆ™ === + usdtPerTree Decimal @map("usdt_per_tree") @db.Decimal(20, 8) + hashpowerPercent Decimal @default(0) @map("hashpower_percent") @db.Decimal(5, 2) + + // === ๅˆ†้…็›ฎๆ ‡ === + payableTo String @map("payable_to") @db.VarChar(50) // USER_ACCOUNT/SYSTEM_ACCOUNT/HEADQUARTERS + + // === ่ง„ๅˆ™ๆ่ฟฐ === + ruleDescription String? @map("rule_description") @db.Text + + // === ๅฏ็”จ็Šถๆ€ === + isEnabled Boolean @default(true) @map("is_enabled") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("right_definitions") + @@index([rightType], name: "idx_def_right_type") + @@index([isEnabled], name: "idx_def_enabled") +} + +// ============================================ +// ็ป“็ฎ—่ฎฐๅฝ•่กจ (่กŒไธบ่กจ) +// ่ฎฐๅฝ•ๆฏๆฌก็ป“็ฎ—็š„่ฏฆๆƒ… +// ============================================ +model SettlementRecord { + id BigInt @id @default(autoincrement()) @map("settlement_id") + userId BigInt @map("user_id") + + // === ็ป“็ฎ—้‡‘้ข === + usdtAmount Decimal @map("usdt_amount") @db.Decimal(20, 8) + hashpowerAmount Decimal @map("hashpower_amount") @db.Decimal(20, 8) + + // === ็ป“็ฎ—ๅธ็ง === + settleCurrency String @map("settle_currency") @db.VarChar(10) // BNB/OG/USDT/DST + receivedAmount Decimal @map("received_amount") @db.Decimal(20, 8) // ๅฎž้™…ๆ”ถๅˆฐ็š„ๅธ็งๆ•ฐ้‡ + + // === ไบคๆ˜“ไฟกๆฏ === + swapTxHash String? @map("swap_tx_hash") @db.VarChar(100) + swapRate Decimal? @map("swap_rate") @db.Decimal(20, 8) // SWAPๆฑ‡็އ + + // === ็Šถๆ€ === + status String @default("PENDING") @map("status") @db.VarChar(20) // PENDING/SUCCESS/FAILED + + // === ๆ—ถ้—ดๆˆณ === + createdAt DateTime @default(now()) @map("created_at") + completedAt DateTime? @map("completed_at") + + // === ๅ…ณ่”็š„ๅฅ–ๅŠฑๆก็›ฎIDๅˆ—่กจ === + rewardEntryIds BigInt[] @map("reward_entry_ids") + + @@map("settlement_records") + @@index([userId], name: "idx_settlement_user") + @@index([status], name: "idx_settlement_status") + @@index([createdAt], name: "idx_settlement_created") +} + +// ============================================ +// ๅฅ–ๅŠฑไบ‹ไปถ่กจ (่กŒไธบ่กจ, append-only) +// ็”จไบŽไบ‹ไปถๆบฏๆบๅ’Œๅฎก่ฎก +// ============================================ +model RewardEvent { + id BigInt @id @default(autoincrement()) @map("event_id") + eventType String @map("event_type") @db.VarChar(50) + + // ่šๅˆๆ นไฟกๆฏ + aggregateId String @map("aggregate_id") @db.VarChar(100) + aggregateType String @map("aggregate_type") @db.VarChar(50) + + // ไบ‹ไปถๆ•ฐๆฎ + eventData Json @map("event_data") + + // ๅ…ƒๆ•ฐๆฎ + userId BigInt? @map("user_id") + occurredAt DateTime @default(now()) @map("occurred_at") @db.Timestamp(6) + version Int @default(1) @map("version") + + @@map("reward_events") + @@index([aggregateType, aggregateId], name: "idx_reward_event_aggregate") + @@index([eventType], name: "idx_reward_event_type") + @@index([userId], name: "idx_reward_event_user") + @@index([occurredAt], name: "idx_reward_event_occurred") +} diff --git a/backend/services/reward-service/prisma/seed.ts b/backend/services/reward-service/prisma/seed.ts new file mode 100644 index 00000000..c7c50534 --- /dev/null +++ b/backend/services/reward-service/prisma/seed.ts @@ -0,0 +1,70 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +async function main() { + // ๅˆๅง‹ๅŒ–ๆƒ็›Šๅฎšไน‰ + const rightDefinitions = [ + { + rightType: 'SHARE_RIGHT', + usdtPerTree: 500, + hashpowerPercent: 0, + payableTo: 'USER_ACCOUNT', + ruleDescription: 'ๅˆ†ไบซๆƒ็›Š๏ผšๆฏๆฃตๆ ‘500 USDT๏ผŒๅˆ†้…็ป™ๆŽจ่้“พ', + }, + { + rightType: 'PROVINCE_AREA_RIGHT', + usdtPerTree: 15, + hashpowerPercent: 1, + payableTo: 'SYSTEM_ACCOUNT', + ruleDescription: '็œๅŒบๅŸŸๆƒ็›Š๏ผšๆฏๆฃตๆ ‘15 USDT + 1%็ฎ—ๅŠ›๏ผŒ่ฟ›็ณป็ปŸ็œๅ…ฌๅธ่ดฆๆˆท', + }, + { + rightType: 'PROVINCE_TEAM_RIGHT', + usdtPerTree: 20, + hashpowerPercent: 0, + payableTo: 'USER_ACCOUNT', + ruleDescription: '็œๅ›ข้˜Ÿๆƒ็›Š๏ผšๆฏๆฃตๆ ‘20 USDT๏ผŒ็ป™ๆœ€่ฟ‘็š„ๆŽˆๆƒ็œๅ…ฌๅธ', + }, + { + rightType: 'CITY_AREA_RIGHT', + usdtPerTree: 35, + hashpowerPercent: 2, + payableTo: 'SYSTEM_ACCOUNT', + ruleDescription: 'ๅธ‚ๅŒบๅŸŸๆƒ็›Š๏ผšๆฏๆฃตๆ ‘35 USDT + 2%็ฎ—ๅŠ›๏ผŒ่ฟ›็ณป็ปŸๅธ‚ๅ…ฌๅธ่ดฆๆˆท', + }, + { + rightType: 'CITY_TEAM_RIGHT', + usdtPerTree: 40, + hashpowerPercent: 0, + payableTo: 'USER_ACCOUNT', + ruleDescription: 'ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š๏ผšๆฏๆฃตๆ ‘40 USDT๏ผŒ็ป™ๆœ€่ฟ‘็š„ๆŽˆๆƒๅธ‚ๅ…ฌๅธ', + }, + { + rightType: 'COMMUNITY_RIGHT', + usdtPerTree: 80, + hashpowerPercent: 0, + payableTo: 'USER_ACCOUNT', + ruleDescription: '็คพๅŒบๆƒ็›Š๏ผšๆฏๆฃตๆ ‘80 USDT๏ผŒ็ป™ๆœ€่ฟ‘็š„็คพๅŒบ', + }, + ]; + + for (const def of rightDefinitions) { + await prisma.rightDefinition.upsert({ + where: { rightType: def.rightType }, + update: def, + create: def, + }); + } + + console.log('Seed completed: Right definitions initialized'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/backend/services/reward-service/src/api/api.module.ts b/backend/services/reward-service/src/api/api.module.ts new file mode 100644 index 00000000..0da636f4 --- /dev/null +++ b/backend/services/reward-service/src/api/api.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { HealthController } from './controllers/health.controller'; +import { RewardController } from './controllers/reward.controller'; +import { SettlementController } from './controllers/settlement.controller'; +import { JwtStrategy } from '../shared/strategies/jwt.strategy'; +import { ApplicationModule } from '../application/application.module'; + +@Module({ + imports: [ + PassportModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + signOptions: { + expiresIn: configService.get('JWT_ACCESS_EXPIRES_IN', '2h') as any, + }, + }), + inject: [ConfigService], + }), + ApplicationModule, + ], + controllers: [HealthController, RewardController, SettlementController], + providers: [JwtStrategy], +}) +export class ApiModule {} diff --git a/backend/services/reward-service/src/api/controllers/health.controller.ts b/backend/services/reward-service/src/api/controllers/health.controller.ts new file mode 100644 index 00000000..f3e4b87a --- /dev/null +++ b/backend/services/reward-service/src/api/controllers/health.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; + +@ApiTags('Health') +@Controller('health') +export class HealthController { + @Get() + @ApiOperation({ summary: 'ๅฅๅบทๆฃ€ๆŸฅ' }) + @ApiResponse({ status: 200, description: 'ๆœๅŠกๆญฃๅธธ' }) + check() { + return { + status: 'ok', + service: 'reward-service', + timestamp: new Date().toISOString(), + }; + } +} diff --git a/backend/services/reward-service/src/api/controllers/reward.controller.ts b/backend/services/reward-service/src/api/controllers/reward.controller.ts new file mode 100644 index 00000000..e15e3bc6 --- /dev/null +++ b/backend/services/reward-service/src/api/controllers/reward.controller.ts @@ -0,0 +1,61 @@ +import { Controller, Get, Query, UseGuards, Request, DefaultValuePipe, ParseIntPipe } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard'; +import { RewardApplicationService } from '../../application/services/reward-application.service'; +import { RewardSummaryDto } from '../dto/response/reward-summary.dto'; +import { RewardEntryListDto } from '../dto/response/reward-entry.dto'; +import { RewardStatus } from '../../domain/value-objects/reward-status.enum'; +import { RightType } from '../../domain/value-objects/right-type.enum'; + +@ApiTags('Rewards') +@Controller('rewards') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class RewardController { + constructor(private readonly rewardService: RewardApplicationService) {} + + @Get('summary') + @ApiOperation({ summary: '่Žทๅ–ๆˆ‘็š„ๆ”ถ็›Šๆฑ‡ๆ€ป' }) + @ApiResponse({ status: 200, description: 'ๆˆๅŠŸ', type: RewardSummaryDto }) + async getSummary(@Request() req): Promise { + const userId = BigInt(req.user.sub); + const summary = await this.rewardService.getRewardSummary(userId); + + return { + ...summary, + pendingRemainingTimeMs: summary.pendingExpireAt + ? Math.max(0, summary.pendingExpireAt.getTime() - Date.now()) + : 0, + }; + } + + @Get('details') + @ApiOperation({ summary: '่Žทๅ–ๆˆ‘็š„ๅฅ–ๅŠฑๆ˜Ž็ป†' }) + @ApiQuery({ name: 'status', required: false, enum: RewardStatus }) + @ApiQuery({ name: 'rightType', required: false, enum: RightType }) + @ApiQuery({ name: 'page', required: false, type: Number }) + @ApiQuery({ name: 'pageSize', required: false, type: Number }) + @ApiResponse({ status: 200, description: 'ๆˆๅŠŸ', type: RewardEntryListDto }) + async getDetails( + @Request() req, + @Query('status') status?: RewardStatus, + @Query('rightType') rightType?: RightType, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1, + @Query('pageSize', new DefaultValuePipe(20), ParseIntPipe) pageSize: number = 20, + ) { + const userId = BigInt(req.user.sub); + const filters: any = {}; + if (status) filters.status = status; + if (rightType) filters.rightType = rightType; + + return this.rewardService.getRewardDetails(userId, filters, { page, pageSize }); + } + + @Get('pending') + @ApiOperation({ summary: '่Žทๅ–ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ๏ผˆๅซๅ€’่ฎกๆ—ถ๏ผ‰' }) + @ApiResponse({ status: 200, description: 'ๆˆๅŠŸ' }) + async getPending(@Request() req) { + const userId = BigInt(req.user.sub); + return this.rewardService.getPendingRewards(userId); + } +} diff --git a/backend/services/reward-service/src/api/controllers/settlement.controller.ts b/backend/services/reward-service/src/api/controllers/settlement.controller.ts new file mode 100644 index 00000000..aafc573b --- /dev/null +++ b/backend/services/reward-service/src/api/controllers/settlement.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard'; +import { RewardApplicationService } from '../../application/services/reward-application.service'; +import { SettleRewardsDto } from '../dto/request/settle-rewards.dto'; +import { SettlementResultDto } from '../dto/response/settlement-result.dto'; + +@ApiTags('Settlement') +@Controller('rewards') +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +export class SettlementController { + constructor(private readonly rewardService: RewardApplicationService) {} + + @Post('settle') + @ApiOperation({ summary: '็ป“็ฎ—ๅฏ็ป“็ฎ—ๆ”ถ็›Š' }) + @ApiResponse({ status: 200, description: 'ๆˆๅŠŸ', type: SettlementResultDto }) + @ApiResponse({ status: 400, description: '็ป“็ฎ—ๅคฑ่ดฅ' }) + async settle( + @Request() req, + @Body() dto: SettleRewardsDto, + ): Promise { + const userId = BigInt(req.user.sub); + + return this.rewardService.settleRewards({ + userId, + settleCurrency: dto.settleCurrency, + }); + } +} diff --git a/backend/services/reward-service/src/api/dto/request/settle-rewards.dto.ts b/backend/services/reward-service/src/api/dto/request/settle-rewards.dto.ts new file mode 100644 index 00000000..95db681f --- /dev/null +++ b/backend/services/reward-service/src/api/dto/request/settle-rewards.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty } from 'class-validator'; + +export enum SettleCurrency { + BNB = 'BNB', + OG = 'OG', + USDT = 'USDT', + DST = 'DST', +} + +export class SettleRewardsDto { + @ApiProperty({ + description: '็ป“็ฎ—ๅธ็ง', + enum: SettleCurrency, + example: SettleCurrency.USDT, + }) + @IsNotEmpty({ message: '่ฏท้€‰ๆ‹ฉ็ป“็ฎ—ๅธ็ง' }) + @IsEnum(SettleCurrency, { message: 'ๆ— ๆ•ˆ็š„็ป“็ฎ—ๅธ็ง' }) + settleCurrency: SettleCurrency; +} diff --git a/backend/services/reward-service/src/api/dto/response/reward-entry.dto.ts b/backend/services/reward-service/src/api/dto/response/reward-entry.dto.ts new file mode 100644 index 00000000..f4734d00 --- /dev/null +++ b/backend/services/reward-service/src/api/dto/response/reward-entry.dto.ts @@ -0,0 +1,53 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { RightType } from '../../../domain/value-objects/right-type.enum'; +import { RewardStatus } from '../../../domain/value-objects/reward-status.enum'; + +export class RewardEntryDto { + @ApiProperty({ description: 'ๅฅ–ๅŠฑๆตๆฐดID', example: '1' }) + id: string; + + @ApiProperty({ description: 'ๆƒ็›Š็ฑปๅž‹', enum: RightType, example: RightType.SHARE_RIGHT }) + rightType: RightType; + + @ApiProperty({ description: 'USDT้‡‘้ข', example: 500 }) + usdtAmount: number; + + @ApiProperty({ description: '็ฎ—ๅŠ›', example: 0 }) + hashpowerAmount: number; + + @ApiProperty({ description: 'ๅฅ–ๅŠฑ็Šถๆ€', enum: RewardStatus, example: RewardStatus.SETTLEABLE }) + rewardStatus: RewardStatus; + + @ApiProperty({ description: 'ๅˆ›ๅปบๆ—ถ้—ด', example: '2024-01-01T00:00:00.000Z' }) + createdAt: Date; + + @ApiProperty({ description: '่ฟ‡ๆœŸๆ—ถ้—ด', example: '2024-01-02T00:00:00.000Z', nullable: true }) + expireAt: Date | null; + + @ApiProperty({ description: 'ๅ‰ฉไฝ™่ฟ‡ๆœŸๆ—ถ้—ด(ๆฏซ็ง’)', example: 86400000 }) + remainingTimeMs: number; + + @ApiProperty({ description: '้ข†ๅ–ๆ—ถ้—ด', example: '2024-01-01T12:00:00.000Z', nullable: true }) + claimedAt: Date | null; + + @ApiProperty({ description: '็ป“็ฎ—ๆ—ถ้—ด', example: '2024-01-01T18:00:00.000Z', nullable: true }) + settledAt: Date | null; + + @ApiProperty({ description: '่ฟ‡ๆœŸๆ—ถ้—ด', example: null, nullable: true }) + expiredAt: Date | null; + + @ApiProperty({ description: 'ๅค‡ๆณจ', example: 'ๅˆ†ไบซๆƒ็›Š๏ผšๆฅ่‡ช็”จๆˆท123็š„่ฎค็ง' }) + memo: string; +} + +export class RewardEntryListDto { + @ApiProperty({ description: 'ๅฅ–ๅŠฑๅˆ—่กจ', type: [RewardEntryDto] }) + data: RewardEntryDto[]; + + @ApiProperty({ description: 'ๅˆ†้กตไฟกๆฏ' }) + pagination: { + page: number; + pageSize: number; + total: number; + }; +} diff --git a/backend/services/reward-service/src/api/dto/response/reward-summary.dto.ts b/backend/services/reward-service/src/api/dto/response/reward-summary.dto.ts new file mode 100644 index 00000000..3d6e688b --- /dev/null +++ b/backend/services/reward-service/src/api/dto/response/reward-summary.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RewardSummaryDto { + @ApiProperty({ description: 'ๅพ…้ข†ๅ–ๆ”ถ็›Š(USDT)', example: 500 }) + pendingUsdt: number; + + @ApiProperty({ description: 'ๅพ…้ข†ๅ–็ฎ—ๅŠ›', example: 0 }) + pendingHashpower: number; + + @ApiProperty({ description: 'ๆœ€ๆ—ฉ่ฟ‡ๆœŸๆ—ถ้—ด', example: '2024-01-01T00:00:00.000Z', nullable: true }) + pendingExpireAt: Date | null; + + @ApiProperty({ description: 'ๅ‰ฉไฝ™่ฟ‡ๆœŸๆ—ถ้—ด(ๆฏซ็ง’)', example: 86400000 }) + pendingRemainingTimeMs: number; + + @ApiProperty({ description: 'ๅฏ็ป“็ฎ—ๆ”ถ็›Š(USDT)', example: 1000 }) + settleableUsdt: number; + + @ApiProperty({ description: 'ๅฏ็ป“็ฎ—็ฎ—ๅŠ›', example: 5 }) + settleableHashpower: number; + + @ApiProperty({ description: 'ๅทฒ็ป“็ฎ—ๆ€ปๆ”ถ็›Š(USDT)', example: 5000 }) + settledTotalUsdt: number; + + @ApiProperty({ description: 'ๅทฒ็ป“็ฎ—ๆ€ป็ฎ—ๅŠ›', example: 20 }) + settledTotalHashpower: number; + + @ApiProperty({ description: 'ๅทฒ่ฟ‡ๆœŸๆ€ปๆ”ถ็›Š(USDT)', example: 200 }) + expiredTotalUsdt: number; + + @ApiProperty({ description: 'ๅทฒ่ฟ‡ๆœŸๆ€ป็ฎ—ๅŠ›', example: 0 }) + expiredTotalHashpower: number; +} diff --git a/backend/services/reward-service/src/api/dto/response/settlement-result.dto.ts b/backend/services/reward-service/src/api/dto/response/settlement-result.dto.ts new file mode 100644 index 00000000..d4cb2f18 --- /dev/null +++ b/backend/services/reward-service/src/api/dto/response/settlement-result.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SettlementResultDto { + @ApiProperty({ description: 'ๆ˜ฏๅฆๆˆๅŠŸ', example: true }) + success: boolean; + + @ApiProperty({ description: '็ป“็ฎ—็š„USDT้‡‘้ข', example: 1000 }) + settledUsdtAmount: number; + + @ApiProperty({ description: 'ๅฎž้™…ๆ”ถๅˆฐ็š„ๅธ็งๆ•ฐ้‡', example: 0.5 }) + receivedAmount: number; + + @ApiProperty({ description: '็ป“็ฎ—ๅธ็ง', example: 'BNB' }) + settleCurrency: string; + + @ApiProperty({ description: 'ไบคๆ˜“ๅ“ˆๅธŒ', example: '0x...', nullable: true }) + txHash?: string; + + @ApiProperty({ description: '้”™่ฏฏไฟกๆฏ', example: null, nullable: true }) + error?: string; +} diff --git a/backend/services/reward-service/src/app.module.ts b/backend/services/reward-service/src/app.module.ts new file mode 100644 index 00000000..b03c2798 --- /dev/null +++ b/backend/services/reward-service/src/app.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { ApiModule } from './api/api.module'; +import { appConfig } from './config'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: ['.env.development', '.env'], + load: [appConfig], + }), + ApiModule, + ], +}) +export class AppModule {} diff --git a/backend/services/reward-service/src/application/application.module.ts b/backend/services/reward-service/src/application/application.module.ts new file mode 100644 index 00000000..77d276f1 --- /dev/null +++ b/backend/services/reward-service/src/application/application.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { RewardApplicationService } from './services/reward-application.service'; +import { RewardExpirationScheduler } from './schedulers/reward-expiration.scheduler'; +import { DomainModule } from '../domain/domain.module'; +import { InfrastructureModule } from '../infrastructure/infrastructure.module'; + +@Module({ + imports: [ + ScheduleModule.forRoot(), + DomainModule, + InfrastructureModule, + ], + providers: [ + RewardApplicationService, + RewardExpirationScheduler, + ], + exports: [RewardApplicationService], +}) +export class ApplicationModule {} diff --git a/backend/services/reward-service/src/application/schedulers/reward-expiration.scheduler.ts b/backend/services/reward-service/src/application/schedulers/reward-expiration.scheduler.ts new file mode 100644 index 00000000..2a2ea970 --- /dev/null +++ b/backend/services/reward-service/src/application/schedulers/reward-expiration.scheduler.ts @@ -0,0 +1,27 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { RewardApplicationService } from '../services/reward-application.service'; + +@Injectable() +export class RewardExpirationScheduler { + private readonly logger = new Logger(RewardExpirationScheduler.name); + + constructor( + private readonly rewardService: RewardApplicationService, + ) {} + + /** + * ๆฏๅฐๆ—ถๆฃ€ๆŸฅ่ฟ‡ๆœŸ็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ + */ + @Cron(CronExpression.EVERY_HOUR) + async handleExpiredRewards() { + this.logger.log('ๅผ€ๅง‹ๆฃ€ๆŸฅ่ฟ‡ๆœŸๅฅ–ๅŠฑ...'); + + try { + const result = await this.rewardService.expireOverdueRewards(); + this.logger.log(`่ฟ‡ๆœŸๅฅ–ๅŠฑๅค„็†ๅฎŒๆˆ๏ผš${result.expiredCount}็ฌ”๏ผŒๅ…ฑ${result.totalUsdtExpired} USDT`); + } catch (error) { + this.logger.error('ๅค„็†่ฟ‡ๆœŸๅฅ–ๅŠฑๆ—ถๅ‘็”Ÿ้”™่ฏฏ:', error); + } + } +} diff --git a/backend/services/reward-service/src/application/services/reward-application.service.ts b/backend/services/reward-service/src/application/services/reward-application.service.ts new file mode 100644 index 00000000..35ae5da1 --- /dev/null +++ b/backend/services/reward-service/src/application/services/reward-application.service.ts @@ -0,0 +1,340 @@ +import { Injectable, Inject, Logger } from '@nestjs/common'; +import { RewardLedgerEntry } from '../../domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { RewardCalculationService } from '../../domain/services/reward-calculation.service'; +import { RewardExpirationService } from '../../domain/services/reward-expiration.service'; +import type { IRewardLedgerEntryRepository } from '../../domain/repositories/reward-ledger-entry.repository.interface'; +import { REWARD_LEDGER_ENTRY_REPOSITORY } from '../../domain/repositories/reward-ledger-entry.repository.interface'; +import type { IRewardSummaryRepository } from '../../domain/repositories/reward-summary.repository.interface'; +import { REWARD_SUMMARY_REPOSITORY } from '../../domain/repositories/reward-summary.repository.interface'; +import { RewardStatus } from '../../domain/value-objects/reward-status.enum'; +import { RightType } from '../../domain/value-objects/right-type.enum'; +import { Money } from '../../domain/value-objects/money.vo'; +import { Hashpower } from '../../domain/value-objects/hashpower.vo'; +import { EventPublisherService } from '../../infrastructure/kafka/event-publisher.service'; +import { WalletServiceClient } from '../../infrastructure/external/wallet-service/wallet-service.client'; + +// ๆ€ป้ƒจ็คพๅŒบ่ดฆๆˆทID +const HEADQUARTERS_COMMUNITY_USER_ID = BigInt(1); + +@Injectable() +export class RewardApplicationService { + private readonly logger = new Logger(RewardApplicationService.name); + + constructor( + private readonly rewardCalculationService: RewardCalculationService, + private readonly rewardExpirationService: RewardExpirationService, + @Inject(REWARD_LEDGER_ENTRY_REPOSITORY) + private readonly rewardLedgerEntryRepository: IRewardLedgerEntryRepository, + @Inject(REWARD_SUMMARY_REPOSITORY) + private readonly rewardSummaryRepository: IRewardSummaryRepository, + private readonly eventPublisher: EventPublisherService, + private readonly walletService: WalletServiceClient, + ) {} + + /** + * ๅˆ†้…ๅฅ–ๅŠฑ (ๅ“ๅบ”่ฎค็ง่ฎขๅ•ๆ”ฏไป˜ๆˆๅŠŸไบ‹ไปถ) + */ + async distributeRewards(params: { + sourceOrderId: bigint; + sourceUserId: bigint; + treeCount: number; + provinceCode: string; + cityCode: string; + }): Promise { + this.logger.log(`Distributing rewards for order ${params.sourceOrderId}`); + + // 1. ่ฎก็ฎ—ๆ‰€ๆœ‰ๅฅ–ๅŠฑ + const rewards = await this.rewardCalculationService.calculateRewards(params); + + // 2. ไฟๅญ˜ๅฅ–ๅŠฑๆตๆฐด + await this.rewardLedgerEntryRepository.saveAll(rewards); + + // 3. ๆ›ดๆ–ฐๅ„็”จๆˆท็š„ๆฑ‡ๆ€ปๆ•ฐๆฎ + const userIds = [...new Set(rewards.map(r => r.userId))]; + for (const userId of userIds) { + const userRewards = rewards.filter(r => r.userId === userId); + const summary = await this.rewardSummaryRepository.getOrCreate(userId); + + for (const reward of userRewards) { + if (reward.isPending) { + summary.addPending( + reward.usdtAmount, + reward.hashpowerAmount, + reward.expireAt!, + ); + } else if (reward.isSettleable) { + summary.addSettleable(reward.usdtAmount, reward.hashpowerAmount); + } + } + + await this.rewardSummaryRepository.save(summary); + } + + // 4. ๅ‘ๅธƒ้ข†ๅŸŸไบ‹ไปถ + for (const reward of rewards) { + await this.eventPublisher.publishAll(reward.domainEvents); + reward.clearDomainEvents(); + } + + this.logger.log(`Distributed ${rewards.length} rewards for order ${params.sourceOrderId}`); + } + + /** + * ็”จๆˆท่ฎค็งๅŽ๏ผŒๅฐ†่ฏฅ็”จๆˆท็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ่ฝฌไธบๅฏ็ป“็ฎ— + */ + async claimPendingRewardsForUser(userId: bigint): Promise<{ + claimedCount: number; + totalUsdtClaimed: number; + }> { + this.logger.log(`Claiming pending rewards for user ${userId}`); + + const pendingRewards = await this.rewardLedgerEntryRepository.findPendingByUserId(userId); + const summary = await this.rewardSummaryRepository.getOrCreate(userId); + + let claimedCount = 0; + let totalUsdtClaimed = 0; + + for (const reward of pendingRewards) { + if (!reward.isExpiredNow()) { + reward.claim(); + await this.rewardLedgerEntryRepository.save(reward); + + summary.movePendingToSettleable(reward.usdtAmount, reward.hashpowerAmount); + + claimedCount++; + totalUsdtClaimed += reward.usdtAmount.amount; + + await this.eventPublisher.publishAll(reward.domainEvents); + reward.clearDomainEvents(); + } + } + + await this.rewardSummaryRepository.save(summary); + + this.logger.log(`Claimed ${claimedCount} rewards for user ${userId}, total ${totalUsdtClaimed} USDT`); + + return { claimedCount, totalUsdtClaimed }; + } + + /** + * ็ป“็ฎ—ๅฏ็ป“็ฎ—ๆ”ถ็›Š + */ + async settleRewards(params: { + userId: bigint; + settleCurrency: string; // BNB/OG/USDT/DST + }): Promise<{ + success: boolean; + settledUsdtAmount: number; + receivedAmount: number; + settleCurrency: string; + txHash?: string; + error?: string; + }> { + this.logger.log(`Settling rewards for user ${params.userId}`); + + // 1. ่Žทๅ–ๅฏ็ป“็ฎ—ๅฅ–ๅŠฑ + const settleableRewards = await this.rewardLedgerEntryRepository.findSettleableByUserId(params.userId); + + if (settleableRewards.length === 0) { + return { + success: false, + settledUsdtAmount: 0, + receivedAmount: 0, + settleCurrency: params.settleCurrency, + error: 'ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š', + }; + } + + // 2. ่ฎก็ฎ—ๆ€ป้‡‘้ข + const totalUsdt = settleableRewards.reduce((sum, r) => sum + r.usdtAmount.amount, 0); + const totalHashpower = settleableRewards.reduce((sum, r) => sum + r.hashpowerAmount.value, 0); + + // 3. ่ฐƒ็”จ้’ฑๅŒ…ๆœๅŠกๆ‰ง่กŒSWAP + const swapResult = await this.walletService.executeSwap({ + userId: params.userId, + usdtAmount: totalUsdt, + targetCurrency: params.settleCurrency, + }); + + if (!swapResult.success) { + return { + success: false, + settledUsdtAmount: totalUsdt, + receivedAmount: 0, + settleCurrency: params.settleCurrency, + error: swapResult.error, + }; + } + + // 4. ๆ›ดๆ–ฐๅฅ–ๅŠฑ็Šถๆ€ไธบๅทฒ็ป“็ฎ— + for (const reward of settleableRewards) { + reward.settle(params.settleCurrency, swapResult.receivedAmount!); + await this.rewardLedgerEntryRepository.save(reward); + await this.eventPublisher.publishAll(reward.domainEvents); + reward.clearDomainEvents(); + } + + // 5. ๆ›ดๆ–ฐๆฑ‡ๆ€ปๆ•ฐๆฎ + const summary = await this.rewardSummaryRepository.getOrCreate(params.userId); + summary.settle(Money.USDT(totalUsdt), Hashpower.create(totalHashpower)); + await this.rewardSummaryRepository.save(summary); + + this.logger.log(`Settled ${totalUsdt} USDT for user ${params.userId}`); + + return { + success: true, + settledUsdtAmount: totalUsdt, + receivedAmount: swapResult.receivedAmount!, + settleCurrency: params.settleCurrency, + txHash: swapResult.txHash, + }; + } + + /** + * ่ฟ‡ๆœŸๅˆฐๆœŸ็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ (ๅฎšๆ—ถไปปๅŠก่ฐƒ็”จ) + */ + async expireOverdueRewards(): Promise<{ + expiredCount: number; + totalUsdtExpired: number; + }> { + this.logger.log('Processing expired rewards'); + + const now = new Date(); + const expiredPendingRewards = await this.rewardLedgerEntryRepository.findExpiredPending(now); + + const expiredRewards = this.rewardExpirationService.expireOverdueRewards(expiredPendingRewards); + + let totalUsdtExpired = 0; + + // ๆŒ‰็”จๆˆทๅˆ†็ป„ๅค„็† + const userRewardsMap = new Map(); + for (const reward of expiredRewards) { + const userId = reward.userId.toString(); + if (!userRewardsMap.has(userId)) { + userRewardsMap.set(userId, []); + } + userRewardsMap.get(userId)!.push(reward); + totalUsdtExpired += reward.usdtAmount.amount; + } + + // ๆ›ดๆ–ฐๆฏไธช็”จๆˆท็š„ๆฑ‡ๆ€ปๆ•ฐๆฎ + for (const [userId, rewards] of userRewardsMap) { + const summary = await this.rewardSummaryRepository.getOrCreate(BigInt(userId)); + + for (const reward of rewards) { + await this.rewardLedgerEntryRepository.save(reward); + summary.movePendingToExpired(reward.usdtAmount, reward.hashpowerAmount); + await this.eventPublisher.publishAll(reward.domainEvents); + reward.clearDomainEvents(); + } + + await this.rewardSummaryRepository.save(summary); + } + + // ๅฐ†่ฟ‡ๆœŸๅฅ–ๅŠฑ่ฝฌๅ…ฅๆ€ป้ƒจ็คพๅŒบ + if (expiredRewards.length > 0) { + const hqSummary = await this.rewardSummaryRepository.getOrCreate(HEADQUARTERS_COMMUNITY_USER_ID); + const totalHqUsdt = expiredRewards.reduce((sum, r) => sum + r.usdtAmount.amount, 0); + const totalHqHashpower = expiredRewards.reduce((sum, r) => sum + r.hashpowerAmount.value, 0); + hqSummary.addSettleable(Money.USDT(totalHqUsdt), Hashpower.create(totalHqHashpower)); + await this.rewardSummaryRepository.save(hqSummary); + } + + this.logger.log(`Expired ${expiredRewards.length} rewards, total ${totalUsdtExpired} USDT`); + + return { + expiredCount: expiredRewards.length, + totalUsdtExpired, + }; + } + + /** + * ่Žทๅ–็”จๆˆทๅฅ–ๅŠฑๆฑ‡ๆ€ป + */ + async getRewardSummary(userId: bigint) { + const summary = await this.rewardSummaryRepository.findByUserId(userId); + + if (!summary) { + return { + pendingUsdt: 0, + pendingHashpower: 0, + pendingExpireAt: null, + settleableUsdt: 0, + settleableHashpower: 0, + settledTotalUsdt: 0, + settledTotalHashpower: 0, + expiredTotalUsdt: 0, + expiredTotalHashpower: 0, + }; + } + + return { + pendingUsdt: summary.pendingUsdt.amount, + pendingHashpower: summary.pendingHashpower.value, + pendingExpireAt: summary.pendingExpireAt, + settleableUsdt: summary.settleableUsdt.amount, + settleableHashpower: summary.settleableHashpower.value, + settledTotalUsdt: summary.settledTotalUsdt.amount, + settledTotalHashpower: summary.settledTotalHashpower.value, + expiredTotalUsdt: summary.expiredTotalUsdt.amount, + expiredTotalHashpower: summary.expiredTotalHashpower.value, + }; + } + + /** + * ่Žทๅ–็”จๆˆทๅฅ–ๅŠฑๆ˜Ž็ป† + */ + async getRewardDetails( + userId: bigint, + filters?: { + status?: RewardStatus; + rightType?: RightType; + startDate?: Date; + endDate?: Date; + }, + pagination?: { page: number; pageSize: number }, + ) { + const rewards = await this.rewardLedgerEntryRepository.findByUserId(userId, filters, pagination); + const total = await this.rewardLedgerEntryRepository.countByUserId(userId, filters?.status); + + return { + data: rewards.map(r => ({ + id: r.id?.toString(), + rightType: r.rewardSource.rightType, + usdtAmount: r.usdtAmount.amount, + hashpowerAmount: r.hashpowerAmount.value, + rewardStatus: r.rewardStatus, + createdAt: r.createdAt, + expireAt: r.expireAt, + remainingTimeMs: r.getRemainingTimeMs(), + claimedAt: r.claimedAt, + settledAt: r.settledAt, + expiredAt: r.expiredAt, + memo: r.memo, + })), + pagination: { + page: pagination?.page || 1, + pageSize: pagination?.pageSize || 20, + total, + }, + }; + } + + /** + * ่Žทๅ–ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ๏ผˆๅซๅ€’่ฎกๆ—ถ๏ผ‰ + */ + async getPendingRewards(userId: bigint) { + const rewards = await this.rewardLedgerEntryRepository.findPendingByUserId(userId); + + return rewards.map(r => ({ + id: r.id?.toString(), + rightType: r.rewardSource.rightType, + usdtAmount: r.usdtAmount.amount, + hashpowerAmount: r.hashpowerAmount.value, + createdAt: r.createdAt, + expireAt: r.expireAt, + remainingTimeMs: r.getRemainingTimeMs(), + memo: r.memo, + })); + } +} diff --git a/backend/services/reward-service/src/config/app.config.ts b/backend/services/reward-service/src/config/app.config.ts new file mode 100644 index 00000000..188753a0 --- /dev/null +++ b/backend/services/reward-service/src/config/app.config.ts @@ -0,0 +1,7 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('app', () => ({ + nodeEnv: process.env.NODE_ENV || 'development', + port: parseInt(process.env.PORT || '3005', 10), + appName: process.env.APP_NAME || 'reward-service', +})); diff --git a/backend/services/reward-service/src/config/index.ts b/backend/services/reward-service/src/config/index.ts new file mode 100644 index 00000000..a2148809 --- /dev/null +++ b/backend/services/reward-service/src/config/index.ts @@ -0,0 +1 @@ +export { default as appConfig } from './app.config'; diff --git a/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/index.ts b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/index.ts new file mode 100644 index 00000000..c45e586b --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/index.ts @@ -0,0 +1 @@ +export * from './reward-ledger-entry.aggregate'; diff --git a/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts new file mode 100644 index 00000000..92ec0344 --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts @@ -0,0 +1,282 @@ +import { DomainEvent } from '../../events/domain-event.base'; +import { RewardCreatedEvent } from '../../events/reward-created.event'; +import { RewardClaimedEvent } from '../../events/reward-claimed.event'; +import { RewardExpiredEvent } from '../../events/reward-expired.event'; +import { RewardSettledEvent } from '../../events/reward-settled.event'; +import { RewardSource } from '../../value-objects/reward-source.vo'; +import { RewardStatus } from '../../value-objects/reward-status.enum'; +import { Money } from '../../value-objects/money.vo'; +import { Hashpower } from '../../value-objects/hashpower.vo'; + +/** + * ๅฅ–ๅŠฑๆตๆฐด่šๅˆๆ น + * + * ไธๅ˜ๅผ: + * 1. ๅพ…้ข†ๅ–ๅฅ–ๅŠฑๅฟ…้กปๅœจ24ๅฐๆ—ถๅ†…่ฎค็ง๏ผŒๅฆๅˆ™่ฟ‡ๆœŸ + * 2. ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ้ข†ๅ–(claim) + * 3. ๅชๆœ‰ๅฏ็ป“็ฎ—็Šถๆ€ๆ‰่ƒฝ็ป“็ฎ—(settle) + * 4. ๅทฒ็ป“็ฎ—/ๅทฒ่ฟ‡ๆœŸ็š„ๅฅ–ๅŠฑ็Šถๆ€ไธๅฏๅ˜ๆ›ด + */ +export class RewardLedgerEntry { + private _id: bigint | null = null; + private readonly _userId: bigint; + private readonly _rewardSource: RewardSource; + private readonly _usdtAmount: Money; + private readonly _hashpowerAmount: Hashpower; + private _rewardStatus: RewardStatus; + private readonly _createdAt: Date; + private _expireAt: Date | null; + private _claimedAt: Date | null; + private _settledAt: Date | null; + private _expiredAt: Date | null; + private readonly _memo: string; + + private _domainEvents: DomainEvent[] = []; + + private constructor( + userId: bigint, + rewardSource: RewardSource, + usdtAmount: Money, + hashpowerAmount: Hashpower, + rewardStatus: RewardStatus, + createdAt: Date, + expireAt: Date | null, + memo: string, + ) { + this._userId = userId; + this._rewardSource = rewardSource; + this._usdtAmount = usdtAmount; + this._hashpowerAmount = hashpowerAmount; + this._rewardStatus = rewardStatus; + this._createdAt = createdAt; + this._expireAt = expireAt; + this._claimedAt = null; + this._settledAt = null; + this._expiredAt = null; + this._memo = memo; + } + + // ============ Getters ============ + get id(): bigint | null { return this._id; } + get userId(): bigint { return this._userId; } + get rewardSource(): RewardSource { return this._rewardSource; } + get usdtAmount(): Money { return this._usdtAmount; } + get hashpowerAmount(): Hashpower { return this._hashpowerAmount; } + get rewardStatus(): RewardStatus { return this._rewardStatus; } + get createdAt(): Date { return this._createdAt; } + get expireAt(): Date | null { return this._expireAt; } + get claimedAt(): Date | null { return this._claimedAt; } + get settledAt(): Date | null { return this._settledAt; } + get expiredAt(): Date | null { return this._expiredAt; } + get memo(): string { return this._memo; } + get domainEvents(): DomainEvent[] { return [...this._domainEvents]; } + + get isPending(): boolean { return this._rewardStatus === RewardStatus.PENDING; } + get isSettleable(): boolean { return this._rewardStatus === RewardStatus.SETTLEABLE; } + get isSettled(): boolean { return this._rewardStatus === RewardStatus.SETTLED; } + get isExpired(): boolean { return this._rewardStatus === RewardStatus.EXPIRED; } + + // ============ ๅทฅๅŽ‚ๆ–นๆณ• ============ + + /** + * ๅˆ›ๅปบๅพ…้ข†ๅ–ๅฅ–ๅŠฑ (24ๅฐๆ—ถๅ€’่ฎกๆ—ถ) + * ็”จไบŽๆŽจ่ไบบๆœช่ฎค็ง็š„ๆƒ…ๅ†ต + */ + static createPending(params: { + userId: bigint; + rewardSource: RewardSource; + usdtAmount: Money; + hashpowerAmount: Hashpower; + memo?: string; + }): RewardLedgerEntry { + const now = new Date(); + const expireAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24ๅฐๆ—ถๅŽ + + const entry = new RewardLedgerEntry( + params.userId, + params.rewardSource, + params.usdtAmount, + params.hashpowerAmount, + RewardStatus.PENDING, + now, + expireAt, + params.memo || '', + ); + + entry._domainEvents.push(new RewardCreatedEvent({ + entryId: entry._id?.toString() || 'temp', + userId: entry._userId.toString(), + sourceOrderId: entry._rewardSource.sourceOrderId.toString(), + sourceUserId: entry._rewardSource.sourceUserId.toString(), + rightType: entry._rewardSource.rightType, + usdtAmount: entry._usdtAmount.amount, + hashpowerAmount: entry._hashpowerAmount.value, + rewardStatus: entry._rewardStatus, + expireAt: entry._expireAt, + })); + + return entry; + } + + /** + * ๅˆ›ๅปบ็›ดๆŽฅๅฏ็ป“็ฎ—ๅฅ–ๅŠฑ (ๆ— ้œ€24ๅฐๆ—ถ็ญ‰ๅพ…) + * ็”จไบŽๆŽจ่ไบบๅทฒ่ฎค็ง็š„ๆƒ…ๅ†ต + */ + static createSettleable(params: { + userId: bigint; + rewardSource: RewardSource; + usdtAmount: Money; + hashpowerAmount: Hashpower; + memo?: string; + }): RewardLedgerEntry { + const entry = new RewardLedgerEntry( + params.userId, + params.rewardSource, + params.usdtAmount, + params.hashpowerAmount, + RewardStatus.SETTLEABLE, + new Date(), + null, + params.memo || '', + ); + + entry._domainEvents.push(new RewardCreatedEvent({ + entryId: entry._id?.toString() || 'temp', + userId: entry._userId.toString(), + sourceOrderId: entry._rewardSource.sourceOrderId.toString(), + sourceUserId: entry._rewardSource.sourceUserId.toString(), + rightType: entry._rewardSource.rightType, + usdtAmount: entry._usdtAmount.amount, + hashpowerAmount: entry._hashpowerAmount.value, + rewardStatus: entry._rewardStatus, + expireAt: null, + })); + + return entry; + } + + // ============ ้ข†ๅŸŸ่กŒไธบ ============ + + /** + * ้ข†ๅ–ๅฅ–ๅŠฑ (็”จๆˆท่ฎค็งๅŽ๏ผŒๅพ…้ข†ๅ– โ†’ ๅฏ็ป“็ฎ—) + */ + claim(): void { + if (this._rewardStatus !== RewardStatus.PENDING) { + throw new Error('ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ้ข†ๅ–'); + } + + if (this.isExpiredNow()) { + throw new Error('ๅฅ–ๅŠฑๅทฒ่ฟ‡ๆœŸ๏ผŒๆ— ๆณ•้ข†ๅ–'); + } + + this._rewardStatus = RewardStatus.SETTLEABLE; + this._claimedAt = new Date(); + this._expireAt = null; + + this._domainEvents.push(new RewardClaimedEvent({ + entryId: this._id?.toString() || '', + userId: this._userId.toString(), + usdtAmount: this._usdtAmount.amount, + hashpowerAmount: this._hashpowerAmount.value, + })); + } + + /** + * ่ฟ‡ๆœŸ (24ๅฐๆ—ถๅŽๆœช่ฎค็ง) + */ + expire(): void { + if (this._rewardStatus !== RewardStatus.PENDING) { + throw new Error('ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ่ฟ‡ๆœŸ'); + } + + this._rewardStatus = RewardStatus.EXPIRED; + this._expiredAt = new Date(); + + this._domainEvents.push(new RewardExpiredEvent({ + entryId: this._id?.toString() || '', + userId: this._userId.toString(), + usdtAmount: this._usdtAmount.amount, + hashpowerAmount: this._hashpowerAmount.value, + transferredTo: 'HEADQUARTERS_COMMUNITY', + })); + } + + /** + * ็ป“็ฎ— (ๅฏ็ป“็ฎ— โ†’ ๅทฒ็ป“็ฎ—) + */ + settle(settleCurrency: string, receivedAmount: number): void { + if (this._rewardStatus !== RewardStatus.SETTLEABLE) { + throw new Error('ๅชๆœ‰ๅฏ็ป“็ฎ—็Šถๆ€ๆ‰่ƒฝ็ป“็ฎ—'); + } + + this._rewardStatus = RewardStatus.SETTLED; + this._settledAt = new Date(); + + this._domainEvents.push(new RewardSettledEvent({ + entryId: this._id?.toString() || '', + userId: this._userId.toString(), + usdtAmount: this._usdtAmount.amount, + hashpowerAmount: this._hashpowerAmount.value, + settleCurrency, + receivedAmount, + })); + } + + /** + * ๆฃ€ๆŸฅๆ˜ฏๅฆๅทฒ่ฟ‡ๆœŸ + */ + isExpiredNow(): boolean { + if (!this._expireAt) return false; + return new Date() > this._expireAt; + } + + /** + * ่Žทๅ–ๅ‰ฉไฝ™่ฟ‡ๆœŸๆ—ถ้—ด (ๆฏซ็ง’) + */ + getRemainingTimeMs(): number { + if (!this._expireAt) return 0; + const remaining = this._expireAt.getTime() - Date.now(); + return Math.max(0, remaining); + } + + setId(id: bigint): void { + this._id = id; + } + + clearDomainEvents(): void { + this._domainEvents = []; + } + + // ============ ้‡ๅปบ ============ + + static reconstitute(data: { + id: bigint; + userId: bigint; + rewardSource: RewardSource; + usdtAmount: number; + hashpowerAmount: number; + rewardStatus: RewardStatus; + createdAt: Date; + expireAt: Date | null; + claimedAt: Date | null; + settledAt: Date | null; + expiredAt: Date | null; + memo: string; + }): RewardLedgerEntry { + const entry = new RewardLedgerEntry( + data.userId, + data.rewardSource, + Money.USDT(data.usdtAmount), + Hashpower.create(data.hashpowerAmount), + data.rewardStatus, + data.createdAt, + data.expireAt, + data.memo, + ); + entry._id = data.id; + entry._claimedAt = data.claimedAt; + entry._settledAt = data.settledAt; + entry._expiredAt = data.expiredAt; + return entry; + } +} diff --git a/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.spec.ts b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.spec.ts new file mode 100644 index 00000000..8e2e2712 --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.spec.ts @@ -0,0 +1,211 @@ +import { RewardLedgerEntry } from './reward-ledger-entry.aggregate'; +import { RewardSource } from '../../value-objects/reward-source.vo'; +import { RightType } from '../../value-objects/right-type.enum'; +import { RewardStatus } from '../../value-objects/reward-status.enum'; +import { Money } from '../../value-objects/money.vo'; +import { Hashpower } from '../../value-objects/hashpower.vo'; + +describe('RewardLedgerEntry', () => { + const createRewardSource = () => + RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(2)); + + describe('createPending', () => { + it('should create a pending reward with 24h expiration', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + memo: 'Test reward', + }); + + expect(entry.isPending).toBe(true); + expect(entry.rewardStatus).toBe(RewardStatus.PENDING); + expect(entry.usdtAmount.amount).toBe(500); + expect(entry.expireAt).toBeDefined(); + expect(entry.domainEvents.length).toBe(1); + expect(entry.domainEvents[0].eventType).toBe('RewardCreated'); + }); + + it('should set expireAt to 24 hours from now', () => { + const before = Date.now(); + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + const after = Date.now(); + + const expectedExpireMin = before + 24 * 60 * 60 * 1000; + const expectedExpireMax = after + 24 * 60 * 60 * 1000; + + expect(entry.expireAt!.getTime()).toBeGreaterThanOrEqual(expectedExpireMin); + expect(entry.expireAt!.getTime()).toBeLessThanOrEqual(expectedExpireMax); + }); + }); + + describe('createSettleable', () => { + it('should create a settleable reward without expiration', () => { + const entry = RewardLedgerEntry.createSettleable({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.create(5), + memo: 'Direct reward', + }); + + expect(entry.isSettleable).toBe(true); + expect(entry.rewardStatus).toBe(RewardStatus.SETTLEABLE); + expect(entry.expireAt).toBeNull(); + expect(entry.hashpowerAmount.value).toBe(5); + }); + }); + + describe('claim', () => { + it('should transition pending to settleable', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + entry.clearDomainEvents(); + + entry.claim(); + + expect(entry.isSettleable).toBe(true); + expect(entry.claimedAt).toBeDefined(); + expect(entry.expireAt).toBeNull(); + expect(entry.domainEvents.length).toBe(1); + expect(entry.domainEvents[0].eventType).toBe('RewardClaimed'); + }); + + it('should throw error when not pending', () => { + const entry = RewardLedgerEntry.createSettleable({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + + expect(() => entry.claim()).toThrow('ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ้ข†ๅ–'); + }); + }); + + describe('expire', () => { + it('should transition pending to expired', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + entry.clearDomainEvents(); + + entry.expire(); + + expect(entry.isExpired).toBe(true); + expect(entry.expiredAt).toBeDefined(); + expect(entry.domainEvents.length).toBe(1); + expect(entry.domainEvents[0].eventType).toBe('RewardExpired'); + }); + + it('should throw error when not pending', () => { + const entry = RewardLedgerEntry.createSettleable({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + + expect(() => entry.expire()).toThrow('ๅชๆœ‰ๅพ…้ข†ๅ–็Šถๆ€ๆ‰่ƒฝ่ฟ‡ๆœŸ'); + }); + }); + + describe('settle', () => { + it('should transition settleable to settled', () => { + const entry = RewardLedgerEntry.createSettleable({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + entry.clearDomainEvents(); + + entry.settle('BNB', 0.25); + + expect(entry.isSettled).toBe(true); + expect(entry.settledAt).toBeDefined(); + expect(entry.domainEvents.length).toBe(1); + expect(entry.domainEvents[0].eventType).toBe('RewardSettled'); + }); + + it('should throw error when not settleable', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + + expect(() => entry.settle('BNB', 0.25)).toThrow('ๅชๆœ‰ๅฏ็ป“็ฎ—็Šถๆ€ๆ‰่ƒฝ็ป“็ฎ—'); + }); + }); + + describe('getRemainingTimeMs', () => { + it('should return remaining time for pending rewards', () => { + const entry = RewardLedgerEntry.createPending({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + + const remaining = entry.getRemainingTimeMs(); + const expected24h = 24 * 60 * 60 * 1000; + + expect(remaining).toBeGreaterThan(expected24h - 1000); // Allow 1 second tolerance + expect(remaining).toBeLessThanOrEqual(expected24h); + }); + + it('should return 0 for settleable rewards', () => { + const entry = RewardLedgerEntry.createSettleable({ + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + }); + + expect(entry.getRemainingTimeMs()).toBe(0); + }); + }); + + describe('reconstitute', () => { + it('should rebuild aggregate from persistence data', () => { + const data = { + id: BigInt(1), + userId: BigInt(100), + rewardSource: createRewardSource(), + usdtAmount: 500, + hashpowerAmount: 5, + rewardStatus: RewardStatus.SETTLEABLE, + createdAt: new Date(), + expireAt: null, + claimedAt: new Date(), + settledAt: null, + expiredAt: null, + memo: 'Test', + }; + + const entry = RewardLedgerEntry.reconstitute(data); + + expect(entry.id).toBe(BigInt(1)); + expect(entry.userId).toBe(BigInt(100)); + expect(entry.usdtAmount.amount).toBe(500); + expect(entry.hashpowerAmount.value).toBe(5); + expect(entry.isSettleable).toBe(true); + expect(entry.domainEvents.length).toBe(0); // No events on reconstitute + }); + }); +}); diff --git a/backend/services/reward-service/src/domain/aggregates/reward-summary/index.ts b/backend/services/reward-service/src/domain/aggregates/reward-summary/index.ts new file mode 100644 index 00000000..866ee7b8 --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-summary/index.ts @@ -0,0 +1 @@ +export * from './reward-summary.aggregate'; diff --git a/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.aggregate.ts b/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.aggregate.ts new file mode 100644 index 00000000..6843bcc9 --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.aggregate.ts @@ -0,0 +1,168 @@ +import { Money } from '../../value-objects/money.vo'; +import { Hashpower } from '../../value-objects/hashpower.vo'; + +/** + * ๅฅ–ๅŠฑๆฑ‡ๆ€ป่šๅˆๆ น + * ็ปดๆŠค็”จๆˆท็š„ๅ„็ฑปๆ”ถ็›Šๆฑ‡ๆ€ปๆ•ฐๆฎ + */ +export class RewardSummary { + private _id: bigint | null = null; + private readonly _userId: bigint; + + // ๅพ…้ข†ๅ–ๆ”ถ็›Š + private _pendingUsdt: Money; + private _pendingHashpower: Hashpower; + private _pendingExpireAt: Date | null; + + // ๅฏ็ป“็ฎ—ๆ”ถ็›Š + private _settleableUsdt: Money; + private _settleableHashpower: Hashpower; + + // ๅทฒ็ป“็ฎ—ๆ”ถ็›Š (็ดฏ่ฎก) + private _settledTotalUsdt: Money; + private _settledTotalHashpower: Hashpower; + + // ๅทฒ่ฟ‡ๆœŸๆ”ถ็›Š (็ดฏ่ฎก) + private _expiredTotalUsdt: Money; + private _expiredTotalHashpower: Hashpower; + + private _lastUpdateAt: Date; + private readonly _createdAt: Date; + + private constructor(userId: bigint) { + this._userId = userId; + this._pendingUsdt = Money.zero(); + this._pendingHashpower = Hashpower.zero(); + this._pendingExpireAt = null; + this._settleableUsdt = Money.zero(); + this._settleableHashpower = Hashpower.zero(); + this._settledTotalUsdt = Money.zero(); + this._settledTotalHashpower = Hashpower.zero(); + this._expiredTotalUsdt = Money.zero(); + this._expiredTotalHashpower = Hashpower.zero(); + this._lastUpdateAt = new Date(); + this._createdAt = new Date(); + } + + // ============ Getters ============ + get id(): bigint | null { return this._id; } + get userId(): bigint { return this._userId; } + get pendingUsdt(): Money { return this._pendingUsdt; } + get pendingHashpower(): Hashpower { return this._pendingHashpower; } + get pendingExpireAt(): Date | null { return this._pendingExpireAt; } + get settleableUsdt(): Money { return this._settleableUsdt; } + get settleableHashpower(): Hashpower { return this._settleableHashpower; } + get settledTotalUsdt(): Money { return this._settledTotalUsdt; } + get settledTotalHashpower(): Hashpower { return this._settledTotalHashpower; } + get expiredTotalUsdt(): Money { return this._expiredTotalUsdt; } + get expiredTotalHashpower(): Hashpower { return this._expiredTotalHashpower; } + get lastUpdateAt(): Date { return this._lastUpdateAt; } + get createdAt(): Date { return this._createdAt; } + + // ============ ๅทฅๅŽ‚ๆ–นๆณ• ============ + + static create(userId: bigint): RewardSummary { + return new RewardSummary(userId); + } + + // ============ ้ข†ๅŸŸ่กŒไธบ ============ + + /** + * ๅขžๅŠ ๅพ…้ข†ๅ–ๆ”ถ็›Š + */ + addPending(usdt: Money, hashpower: Hashpower, expireAt: Date): void { + this._pendingUsdt = this._pendingUsdt.add(usdt); + this._pendingHashpower = this._pendingHashpower.add(hashpower); + // ๆ›ดๆ–ฐไธบๆœ€ๆ—ฉ็š„่ฟ‡ๆœŸๆ—ถ้—ด + if (!this._pendingExpireAt || expireAt < this._pendingExpireAt) { + this._pendingExpireAt = expireAt; + } + this._lastUpdateAt = new Date(); + } + + /** + * ๅพ…้ข†ๅ– โ†’ ๅฏ็ป“็ฎ— (็”จๆˆท่ฎค็ง) + */ + movePendingToSettleable(usdt: Money, hashpower: Hashpower): void { + this._pendingUsdt = this._pendingUsdt.subtract(usdt); + this._pendingHashpower = this._pendingHashpower.subtract(hashpower); + this._settleableUsdt = this._settleableUsdt.add(usdt); + this._settleableHashpower = this._settleableHashpower.add(hashpower); + + // ๅฆ‚ๆžœๅพ…้ข†ๅ–ๆธ…็ฉบไบ†๏ผŒๆธ…้™ค่ฟ‡ๆœŸๆ—ถ้—ด + if (this._pendingUsdt.isZero()) { + this._pendingExpireAt = null; + } + this._lastUpdateAt = new Date(); + } + + /** + * ๅพ…้ข†ๅ– โ†’ ๅทฒ่ฟ‡ๆœŸ + */ + movePendingToExpired(usdt: Money, hashpower: Hashpower): void { + this._pendingUsdt = this._pendingUsdt.subtract(usdt); + this._pendingHashpower = this._pendingHashpower.subtract(hashpower); + this._expiredTotalUsdt = this._expiredTotalUsdt.add(usdt); + this._expiredTotalHashpower = this._expiredTotalHashpower.add(hashpower); + + if (this._pendingUsdt.isZero()) { + this._pendingExpireAt = null; + } + this._lastUpdateAt = new Date(); + } + + /** + * ๅขžๅŠ ๅฏ็ป“็ฎ—ๆ”ถ็›Š (็›ดๆŽฅๅฏ็ป“็ฎ—็š„ๅฅ–ๅŠฑ) + */ + addSettleable(usdt: Money, hashpower: Hashpower): void { + this._settleableUsdt = this._settleableUsdt.add(usdt); + this._settleableHashpower = this._settleableHashpower.add(hashpower); + this._lastUpdateAt = new Date(); + } + + /** + * ๅฏ็ป“็ฎ— โ†’ ๅทฒ็ป“็ฎ— + */ + settle(usdt: Money, hashpower: Hashpower): void { + this._settleableUsdt = this._settleableUsdt.subtract(usdt); + this._settleableHashpower = this._settleableHashpower.subtract(hashpower); + this._settledTotalUsdt = this._settledTotalUsdt.add(usdt); + this._settledTotalHashpower = this._settledTotalHashpower.add(hashpower); + this._lastUpdateAt = new Date(); + } + + setId(id: bigint): void { + this._id = id; + } + + // ============ ้‡ๅปบ ============ + + static reconstitute(data: { + id: bigint; + userId: bigint; + pendingUsdt: number; + pendingHashpower: number; + pendingExpireAt: Date | null; + settleableUsdt: number; + settleableHashpower: number; + settledTotalUsdt: number; + settledTotalHashpower: number; + expiredTotalUsdt: number; + expiredTotalHashpower: number; + lastUpdateAt: Date; + createdAt: Date; + }): RewardSummary { + const summary = new RewardSummary(data.userId); + summary._id = data.id; + summary._pendingUsdt = Money.USDT(data.pendingUsdt); + summary._pendingHashpower = Hashpower.create(data.pendingHashpower); + summary._pendingExpireAt = data.pendingExpireAt; + summary._settleableUsdt = Money.USDT(data.settleableUsdt); + summary._settleableHashpower = Hashpower.create(data.settleableHashpower); + summary._settledTotalUsdt = Money.USDT(data.settledTotalUsdt); + summary._settledTotalHashpower = Hashpower.create(data.settledTotalHashpower); + summary._expiredTotalUsdt = Money.USDT(data.expiredTotalUsdt); + summary._expiredTotalHashpower = Hashpower.create(data.expiredTotalHashpower); + return summary; + } +} diff --git a/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.spec.ts b/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.spec.ts new file mode 100644 index 00000000..5ce2bcf7 --- /dev/null +++ b/backend/services/reward-service/src/domain/aggregates/reward-summary/reward-summary.spec.ts @@ -0,0 +1,149 @@ +import { RewardSummary } from './reward-summary.aggregate'; +import { Money } from '../../value-objects/money.vo'; +import { Hashpower } from '../../value-objects/hashpower.vo'; + +describe('RewardSummary', () => { + describe('create', () => { + it('should create a new summary with zero values', () => { + const summary = RewardSummary.create(BigInt(100)); + + expect(summary.userId).toBe(BigInt(100)); + expect(summary.pendingUsdt.amount).toBe(0); + expect(summary.settleableUsdt.amount).toBe(0); + expect(summary.settledTotalUsdt.amount).toBe(0); + expect(summary.expiredTotalUsdt.amount).toBe(0); + }); + }); + + describe('addPending', () => { + it('should add pending rewards and update expire time', () => { + const summary = RewardSummary.create(BigInt(100)); + const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000); + + summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt); + + expect(summary.pendingUsdt.amount).toBe(500); + expect(summary.pendingHashpower.value).toBe(5); + expect(summary.pendingExpireAt).toEqual(expireAt); + }); + + it('should keep earliest expire time', () => { + const summary = RewardSummary.create(BigInt(100)); + const earlyExpire = new Date(Date.now() + 12 * 60 * 60 * 1000); + const lateExpire = new Date(Date.now() + 24 * 60 * 60 * 1000); + + summary.addPending(Money.USDT(500), Hashpower.zero(), earlyExpire); + summary.addPending(Money.USDT(300), Hashpower.zero(), lateExpire); + + expect(summary.pendingExpireAt).toEqual(earlyExpire); + expect(summary.pendingUsdt.amount).toBe(800); + }); + }); + + describe('movePendingToSettleable', () => { + it('should move amounts from pending to settleable', () => { + const summary = RewardSummary.create(BigInt(100)); + const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000); + + summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt); + summary.movePendingToSettleable(Money.USDT(500), Hashpower.create(5)); + + expect(summary.pendingUsdt.amount).toBe(0); + expect(summary.pendingHashpower.value).toBe(0); + expect(summary.settleableUsdt.amount).toBe(500); + expect(summary.settleableHashpower.value).toBe(5); + expect(summary.pendingExpireAt).toBeNull(); // Cleared when pending is zero + }); + + it('should partially move pending amounts', () => { + const summary = RewardSummary.create(BigInt(100)); + const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000); + + summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt); + summary.movePendingToSettleable(Money.USDT(300), Hashpower.create(3)); + + expect(summary.pendingUsdt.amount).toBe(200); + expect(summary.pendingHashpower.value).toBe(2); + expect(summary.settleableUsdt.amount).toBe(300); + expect(summary.settleableHashpower.value).toBe(3); + }); + }); + + describe('movePendingToExpired', () => { + it('should move amounts from pending to expired', () => { + const summary = RewardSummary.create(BigInt(100)); + const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000); + + summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt); + summary.movePendingToExpired(Money.USDT(500), Hashpower.create(5)); + + expect(summary.pendingUsdt.amount).toBe(0); + expect(summary.expiredTotalUsdt.amount).toBe(500); + expect(summary.expiredTotalHashpower.value).toBe(5); + }); + }); + + describe('addSettleable', () => { + it('should add directly to settleable', () => { + const summary = RewardSummary.create(BigInt(100)); + + summary.addSettleable(Money.USDT(1000), Hashpower.create(10)); + + expect(summary.settleableUsdt.amount).toBe(1000); + expect(summary.settleableHashpower.value).toBe(10); + }); + }); + + describe('settle', () => { + it('should move settleable to settled total', () => { + const summary = RewardSummary.create(BigInt(100)); + + summary.addSettleable(Money.USDT(1000), Hashpower.create(10)); + summary.settle(Money.USDT(1000), Hashpower.create(10)); + + expect(summary.settleableUsdt.amount).toBe(0); + expect(summary.settledTotalUsdt.amount).toBe(1000); + expect(summary.settledTotalHashpower.value).toBe(10); + }); + + it('should accumulate settled totals', () => { + const summary = RewardSummary.create(BigInt(100)); + + summary.addSettleable(Money.USDT(1000), Hashpower.create(10)); + summary.settle(Money.USDT(500), Hashpower.create(5)); + summary.settle(Money.USDT(300), Hashpower.create(3)); + + expect(summary.settleableUsdt.amount).toBe(200); + expect(summary.settledTotalUsdt.amount).toBe(800); + expect(summary.settledTotalHashpower.value).toBe(8); + }); + }); + + describe('reconstitute', () => { + it('should rebuild aggregate from persistence data', () => { + const data = { + id: BigInt(1), + userId: BigInt(100), + pendingUsdt: 500, + pendingHashpower: 5, + pendingExpireAt: new Date(), + settleableUsdt: 1000, + settleableHashpower: 10, + settledTotalUsdt: 5000, + settledTotalHashpower: 50, + expiredTotalUsdt: 200, + expiredTotalHashpower: 2, + lastUpdateAt: new Date(), + createdAt: new Date(), + }; + + const summary = RewardSummary.reconstitute(data); + + expect(summary.id).toBe(BigInt(1)); + expect(summary.pendingUsdt.amount).toBe(500); + expect(summary.settleableUsdt.amount).toBe(1000); + expect(summary.settledTotalUsdt.amount).toBe(5000); + expect(summary.expiredTotalUsdt.amount).toBe(200); + }); + }); +}); diff --git a/backend/services/reward-service/src/domain/domain.module.ts b/backend/services/reward-service/src/domain/domain.module.ts new file mode 100644 index 00000000..14646ce4 --- /dev/null +++ b/backend/services/reward-service/src/domain/domain.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { RewardCalculationService } from './services/reward-calculation.service'; +import { RewardExpirationService } from './services/reward-expiration.service'; + +@Module({ + providers: [ + RewardCalculationService, + RewardExpirationService, + ], + exports: [ + RewardCalculationService, + RewardExpirationService, + ], +}) +export class DomainModule {} diff --git a/backend/services/reward-service/src/domain/events/domain-event.base.ts b/backend/services/reward-service/src/domain/events/domain-event.base.ts new file mode 100644 index 00000000..ce09b9b6 --- /dev/null +++ b/backend/services/reward-service/src/domain/events/domain-event.base.ts @@ -0,0 +1,18 @@ +import { v4 as uuidv4 } from 'uuid'; + +export abstract class DomainEvent { + public readonly eventId: string; + public readonly occurredAt: Date; + public readonly version: number; + + protected constructor(version: number = 1) { + this.eventId = uuidv4(); + this.occurredAt = new Date(); + this.version = version; + } + + abstract get eventType(): string; + abstract get aggregateId(): string; + abstract get aggregateType(): string; + abstract toPayload(): Record; +} diff --git a/backend/services/reward-service/src/domain/events/index.ts b/backend/services/reward-service/src/domain/events/index.ts new file mode 100644 index 00000000..f2d09fff --- /dev/null +++ b/backend/services/reward-service/src/domain/events/index.ts @@ -0,0 +1,5 @@ +export * from './domain-event.base'; +export * from './reward-created.event'; +export * from './reward-claimed.event'; +export * from './reward-expired.event'; +export * from './reward-settled.event'; diff --git a/backend/services/reward-service/src/domain/events/reward-claimed.event.ts b/backend/services/reward-service/src/domain/events/reward-claimed.event.ts new file mode 100644 index 00000000..2ef31c7d --- /dev/null +++ b/backend/services/reward-service/src/domain/events/reward-claimed.event.ts @@ -0,0 +1,30 @@ +import { DomainEvent } from './domain-event.base'; + +export interface RewardClaimedPayload { + entryId: string; + userId: string; + usdtAmount: number; + hashpowerAmount: number; +} + +export class RewardClaimedEvent extends DomainEvent { + constructor(private readonly payload: RewardClaimedPayload) { + super(); + } + + get eventType(): string { + return 'RewardClaimed'; + } + + get aggregateId(): string { + return this.payload.entryId; + } + + get aggregateType(): string { + return 'RewardLedgerEntry'; + } + + toPayload(): RewardClaimedPayload { + return { ...this.payload }; + } +} diff --git a/backend/services/reward-service/src/domain/events/reward-created.event.ts b/backend/services/reward-service/src/domain/events/reward-created.event.ts new file mode 100644 index 00000000..b72737a2 --- /dev/null +++ b/backend/services/reward-service/src/domain/events/reward-created.event.ts @@ -0,0 +1,37 @@ +import { DomainEvent } from './domain-event.base'; +import { RightType } from '../value-objects/right-type.enum'; +import { RewardStatus } from '../value-objects/reward-status.enum'; + +export interface RewardCreatedPayload { + entryId: string; + userId: string; + sourceOrderId: string; + sourceUserId: string; + rightType: RightType; + usdtAmount: number; + hashpowerAmount: number; + rewardStatus: RewardStatus; + expireAt: Date | null; +} + +export class RewardCreatedEvent extends DomainEvent { + constructor(private readonly payload: RewardCreatedPayload) { + super(); + } + + get eventType(): string { + return 'RewardCreated'; + } + + get aggregateId(): string { + return this.payload.entryId; + } + + get aggregateType(): string { + return 'RewardLedgerEntry'; + } + + toPayload(): RewardCreatedPayload { + return { ...this.payload }; + } +} diff --git a/backend/services/reward-service/src/domain/events/reward-expired.event.ts b/backend/services/reward-service/src/domain/events/reward-expired.event.ts new file mode 100644 index 00000000..3854146a --- /dev/null +++ b/backend/services/reward-service/src/domain/events/reward-expired.event.ts @@ -0,0 +1,31 @@ +import { DomainEvent } from './domain-event.base'; + +export interface RewardExpiredPayload { + entryId: string; + userId: string; + usdtAmount: number; + hashpowerAmount: number; + transferredTo: string; // ่ฝฌ็งปๅˆฐ็š„็›ฎๆ ‡่ดฆๆˆท (ๆ€ป้ƒจ็คพๅŒบ) +} + +export class RewardExpiredEvent extends DomainEvent { + constructor(private readonly payload: RewardExpiredPayload) { + super(); + } + + get eventType(): string { + return 'RewardExpired'; + } + + get aggregateId(): string { + return this.payload.entryId; + } + + get aggregateType(): string { + return 'RewardLedgerEntry'; + } + + toPayload(): RewardExpiredPayload { + return { ...this.payload }; + } +} diff --git a/backend/services/reward-service/src/domain/events/reward-settled.event.ts b/backend/services/reward-service/src/domain/events/reward-settled.event.ts new file mode 100644 index 00000000..a3135037 --- /dev/null +++ b/backend/services/reward-service/src/domain/events/reward-settled.event.ts @@ -0,0 +1,32 @@ +import { DomainEvent } from './domain-event.base'; + +export interface RewardSettledPayload { + entryId: string; + userId: string; + usdtAmount: number; + hashpowerAmount: number; + settleCurrency: string; + receivedAmount: number; +} + +export class RewardSettledEvent extends DomainEvent { + constructor(private readonly payload: RewardSettledPayload) { + super(); + } + + get eventType(): string { + return 'RewardSettled'; + } + + get aggregateId(): string { + return this.payload.entryId; + } + + get aggregateType(): string { + return 'RewardLedgerEntry'; + } + + toPayload(): RewardSettledPayload { + return { ...this.payload }; + } +} diff --git a/backend/services/reward-service/src/domain/repositories/index.ts b/backend/services/reward-service/src/domain/repositories/index.ts new file mode 100644 index 00000000..5dbd3615 --- /dev/null +++ b/backend/services/reward-service/src/domain/repositories/index.ts @@ -0,0 +1,2 @@ +export * from './reward-ledger-entry.repository.interface'; +export * from './reward-summary.repository.interface'; diff --git a/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts b/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts new file mode 100644 index 00000000..84a05df2 --- /dev/null +++ b/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts @@ -0,0 +1,26 @@ +import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { RewardStatus } from '../value-objects/reward-status.enum'; +import { RightType } from '../value-objects/right-type.enum'; + +export interface IRewardLedgerEntryRepository { + save(entry: RewardLedgerEntry): Promise; + saveAll(entries: RewardLedgerEntry[]): Promise; + findById(entryId: bigint): Promise; + findByUserId( + userId: bigint, + filters?: { + status?: RewardStatus; + rightType?: RightType; + startDate?: Date; + endDate?: Date; + }, + pagination?: { page: number; pageSize: number }, + ): Promise; + findPendingByUserId(userId: bigint): Promise; + findSettleableByUserId(userId: bigint): Promise; + findExpiredPending(beforeDate: Date): Promise; + findBySourceOrderId(sourceOrderId: bigint): Promise; + countByUserId(userId: bigint, status?: RewardStatus): Promise; +} + +export const REWARD_LEDGER_ENTRY_REPOSITORY = Symbol('IRewardLedgerEntryRepository'); diff --git a/backend/services/reward-service/src/domain/repositories/reward-summary.repository.interface.ts b/backend/services/reward-service/src/domain/repositories/reward-summary.repository.interface.ts new file mode 100644 index 00000000..bf0dd78e --- /dev/null +++ b/backend/services/reward-service/src/domain/repositories/reward-summary.repository.interface.ts @@ -0,0 +1,11 @@ +import { RewardSummary } from '../aggregates/reward-summary/reward-summary.aggregate'; + +export interface IRewardSummaryRepository { + save(summary: RewardSummary): Promise; + findByUserId(userId: bigint): Promise; + getOrCreate(userId: bigint): Promise; + findByUserIds(userIds: bigint[]): Promise>; + findTopSettleableUsers(limit: number): Promise; +} + +export const REWARD_SUMMARY_REPOSITORY = Symbol('IRewardSummaryRepository'); diff --git a/backend/services/reward-service/src/domain/services/index.ts b/backend/services/reward-service/src/domain/services/index.ts new file mode 100644 index 00000000..91658cf6 --- /dev/null +++ b/backend/services/reward-service/src/domain/services/index.ts @@ -0,0 +1,2 @@ +export * from './reward-calculation.service'; +export * from './reward-expiration.service'; diff --git a/backend/services/reward-service/src/domain/services/reward-calculation.service.ts b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts new file mode 100644 index 00000000..4d4f89ab --- /dev/null +++ b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts @@ -0,0 +1,347 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { RewardSource } from '../value-objects/reward-source.vo'; +import { RightType, RIGHT_AMOUNTS } from '../value-objects/right-type.enum'; +import { Money } from '../value-objects/money.vo'; +import { Hashpower } from '../value-objects/hashpower.vo'; + +// ๅค–้ƒจๆœๅŠกๆŽฅๅฃ (้˜ฒ่…ๅฑ‚) +export interface IReferralServiceClient { + getReferralChain(userId: bigint): Promise<{ + ancestors: Array<{ userId: bigint; hasPlanted: boolean }>; + }>; +} + +export interface IAuthorizationServiceClient { + findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise; + findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise; + findNearestCommunity(userId: bigint): Promise; +} + +export const REFERRAL_SERVICE_CLIENT = Symbol('IReferralServiceClient'); +export const AUTHORIZATION_SERVICE_CLIENT = Symbol('IAuthorizationServiceClient'); + +// ๆ€ป้ƒจ็คพๅŒบ่ดฆๆˆทID +const HEADQUARTERS_COMMUNITY_USER_ID = BigInt(1); + +@Injectable() +export class RewardCalculationService { + constructor( + @Inject(REFERRAL_SERVICE_CLIENT) + private readonly referralService: IReferralServiceClient, + @Inject(AUTHORIZATION_SERVICE_CLIENT) + private readonly authorizationService: IAuthorizationServiceClient, + ) {} + + /** + * ่ฎก็ฎ—่ฎค็ง่ฎขๅ•ไบง็”Ÿ็š„ๆ‰€ๆœ‰ๅฅ–ๅŠฑ + */ + async calculateRewards(params: { + sourceOrderId: bigint; + sourceUserId: bigint; + treeCount: number; + provinceCode: string; + cityCode: string; + }): Promise { + const rewards: RewardLedgerEntry[] = []; + + // 1. ๅˆ†ไบซๆƒ็›Š (500 USDT) + const shareRewards = await this.calculateShareRights( + params.sourceOrderId, + params.sourceUserId, + params.treeCount, + ); + rewards.push(...shareRewards); + + // 2. ็œๅ›ข้˜Ÿๆƒ็›Š (20 USDT) + const provinceTeamReward = await this.calculateProvinceTeamRight( + params.sourceOrderId, + params.sourceUserId, + params.provinceCode, + params.treeCount, + ); + rewards.push(provinceTeamReward); + + // 3. ็œๅŒบๅŸŸๆƒ็›Š (15 USDT + 1%็ฎ—ๅŠ›) + const provinceAreaReward = this.calculateProvinceAreaRight( + params.sourceOrderId, + params.sourceUserId, + params.provinceCode, + params.treeCount, + ); + rewards.push(provinceAreaReward); + + // 4. ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š (40 USDT) + const cityTeamReward = await this.calculateCityTeamRight( + params.sourceOrderId, + params.sourceUserId, + params.cityCode, + params.treeCount, + ); + rewards.push(cityTeamReward); + + // 5. ๅธ‚ๅŒบๅŸŸๆƒ็›Š (35 USDT + 2%็ฎ—ๅŠ›) + const cityAreaReward = this.calculateCityAreaRight( + params.sourceOrderId, + params.sourceUserId, + params.cityCode, + params.treeCount, + ); + rewards.push(cityAreaReward); + + // 6. ็คพๅŒบๆƒ็›Š (80 USDT) + const communityReward = await this.calculateCommunityRight( + params.sourceOrderId, + params.sourceUserId, + params.treeCount, + ); + rewards.push(communityReward); + + return rewards; + } + + /** + * ่ฎก็ฎ—ๅˆ†ไบซๆƒ็›Š (500 USDT) + */ + private async calculateShareRights( + sourceOrderId: bigint, + sourceUserId: bigint, + treeCount: number, + ): Promise { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.SHARE_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.SHARE_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ่Žทๅ–ๆŽจ่้“พ + const referralChain = await this.referralService.getReferralChain(sourceUserId); + + if (referralChain.ancestors.length > 0) { + const directReferrer = referralChain.ancestors[0]; + + if (directReferrer.hasPlanted) { + // ๆŽจ่ไบบๅทฒ่ฎค็ง๏ผŒ็›ดๆŽฅๅฏ็ป“็ฎ— + return [RewardLedgerEntry.createSettleable({ + userId: directReferrer.userId, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `ๅˆ†ไบซๆƒ็›Š๏ผšๆฅ่‡ช็”จๆˆท${sourceUserId}็š„่ฎค็ง`, + })]; + } else { + // ๆŽจ่ไบบๆœช่ฎค็ง๏ผŒ่ฟ›ๅ…ฅๅพ…้ข†ๅ–๏ผˆ24hๅ€’่ฎกๆ—ถ๏ผ‰ + return [RewardLedgerEntry.createPending({ + userId: directReferrer.userId, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `ๅˆ†ไบซๆƒ็›Š๏ผšๆฅ่‡ช็”จๆˆท${sourceUserId}็š„่ฎค็ง๏ผˆ24hๅ†…่ฎค็งๅฏ้ข†ๅ–๏ผ‰`, + })]; + } + } else { + // ๆ— ๆŽจ่ไบบ๏ผŒ่ฟ›ๆ€ป้ƒจ็คพๅŒบ + return [RewardLedgerEntry.createSettleable({ + userId: HEADQUARTERS_COMMUNITY_USER_ID, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: 'ๅˆ†ไบซๆƒ็›Š๏ผšๆ— ๆŽจ่ไบบ๏ผŒ่ฟ›ๆ€ป้ƒจ็คพๅŒบ', + })]; + } + } + + /** + * ่ฎก็ฎ—็œๅ›ข้˜Ÿๆƒ็›Š (20 USDT) + */ + private async calculateProvinceTeamRight( + sourceOrderId: bigint, + sourceUserId: bigint, + provinceCode: string, + treeCount: number, + ): Promise { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_TEAM_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.PROVINCE_TEAM_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ๆŸฅๆ‰พๆœ€่ฟ‘็š„ๆŽˆๆƒ็œๅ…ฌๅธ + const nearestProvince = await this.authorizationService.findNearestAuthorizedProvince( + sourceUserId, + provinceCode, + ); + + if (nearestProvince) { + return RewardLedgerEntry.createSettleable({ + userId: nearestProvince, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `็œๅ›ข้˜Ÿๆƒ็›Š๏ผšๆฅ่‡ช${provinceCode}็œ็š„่ฎค็ง`, + }); + } else { + return RewardLedgerEntry.createSettleable({ + userId: HEADQUARTERS_COMMUNITY_USER_ID, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: '็œๅ›ข้˜Ÿๆƒ็›Š๏ผšๆ— ่พพๆ ‡ๆŽˆๆƒ็œๅ…ฌๅธ๏ผŒ่ฟ›ๆ€ป้ƒจ็คพๅŒบ', + }); + } + } + + /** + * ่ฎก็ฎ—็œๅŒบๅŸŸๆƒ็›Š (15 USDT + 1%็ฎ—ๅŠ›) + */ + private calculateProvinceAreaRight( + sourceOrderId: bigint, + sourceUserId: bigint, + provinceCode: string, + treeCount: number, + ): RewardLedgerEntry { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_AREA_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.PROVINCE_AREA_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ่ฟ›็ณป็ปŸ็œๅ…ฌๅธ่ดฆๆˆท (ไฝฟ็”จ็‰นๆฎŠ่ดฆๆˆทIDๆ ผๅผ) + const systemProvinceAccountId = BigInt(`9${provinceCode.padStart(6, '0')}`); + + return RewardLedgerEntry.createSettleable({ + userId: systemProvinceAccountId, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `็œๅŒบๅŸŸๆƒ็›Š๏ผš${provinceCode}็œ๏ผŒ15U + 1%็ฎ—ๅŠ›`, + }); + } + + /** + * ่ฎก็ฎ—ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š (40 USDT) + */ + private async calculateCityTeamRight( + sourceOrderId: bigint, + sourceUserId: bigint, + cityCode: string, + treeCount: number, + ): Promise { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_TEAM_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.CITY_TEAM_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ๆŸฅๆ‰พๆœ€่ฟ‘็š„ๆŽˆๆƒๅธ‚ๅ…ฌๅธ + const nearestCity = await this.authorizationService.findNearestAuthorizedCity( + sourceUserId, + cityCode, + ); + + if (nearestCity) { + return RewardLedgerEntry.createSettleable({ + userId: nearestCity, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š๏ผšๆฅ่‡ช${cityCode}ๅธ‚็š„่ฎค็ง`, + }); + } else { + return RewardLedgerEntry.createSettleable({ + userId: HEADQUARTERS_COMMUNITY_USER_ID, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: 'ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š๏ผšๆ— ่พพๆ ‡ๆŽˆๆƒๅธ‚ๅ…ฌๅธ๏ผŒ่ฟ›ๆ€ป้ƒจ็คพๅŒบ', + }); + } + } + + /** + * ่ฎก็ฎ—ๅธ‚ๅŒบๅŸŸๆƒ็›Š (35 USDT + 2%็ฎ—ๅŠ›) + */ + private calculateCityAreaRight( + sourceOrderId: bigint, + sourceUserId: bigint, + cityCode: string, + treeCount: number, + ): RewardLedgerEntry { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_AREA_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.CITY_AREA_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ่ฟ›็ณป็ปŸๅธ‚ๅ…ฌๅธ่ดฆๆˆท + const systemCityAccountId = BigInt(`8${cityCode.padStart(6, '0')}`); + + return RewardLedgerEntry.createSettleable({ + userId: systemCityAccountId, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: `ๅธ‚ๅŒบๅŸŸๆƒ็›Š๏ผš${cityCode}ๅธ‚๏ผŒ35U + 2%็ฎ—ๅŠ›`, + }); + } + + /** + * ่ฎก็ฎ—็คพๅŒบๆƒ็›Š (80 USDT) + */ + private async calculateCommunityRight( + sourceOrderId: bigint, + sourceUserId: bigint, + treeCount: number, + ): Promise { + const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.COMMUNITY_RIGHT]; + const usdtAmount = Money.USDT(usdt * treeCount); + const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.COMMUNITY_RIGHT, + sourceOrderId, + sourceUserId, + ); + + // ๆŸฅๆ‰พๆœ€่ฟ‘็š„็คพๅŒบ + const nearestCommunity = await this.authorizationService.findNearestCommunity(sourceUserId); + + if (nearestCommunity) { + return RewardLedgerEntry.createSettleable({ + userId: nearestCommunity, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: '็คพๅŒบๆƒ็›Š๏ผšๆฅ่‡ช็คพๅŒบๆˆๅ‘˜็š„่ฎค็ง', + }); + } else { + return RewardLedgerEntry.createSettleable({ + userId: HEADQUARTERS_COMMUNITY_USER_ID, + rewardSource, + usdtAmount, + hashpowerAmount: hashpower, + memo: '็คพๅŒบๆƒ็›Š๏ผšๆ— ๅฝ’ๅฑž็คพๅŒบ๏ผŒ่ฟ›ๆ€ป้ƒจ็คพๅŒบ', + }); + } + } +} diff --git a/backend/services/reward-service/src/domain/services/reward-expiration.service.ts b/backend/services/reward-service/src/domain/services/reward-expiration.service.ts new file mode 100644 index 00000000..272ad7d7 --- /dev/null +++ b/backend/services/reward-service/src/domain/services/reward-expiration.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; + +@Injectable() +export class RewardExpirationService { + /** + * ๆฃ€ๆŸฅๅนถ่ฟ‡ๆœŸๆ‰€ๆœ‰ๅˆฐๆœŸ็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ + */ + expireOverdueRewards(pendingRewards: RewardLedgerEntry[]): RewardLedgerEntry[] { + const expiredRewards: RewardLedgerEntry[] = []; + + for (const reward of pendingRewards) { + if (reward.isExpiredNow()) { + reward.expire(); + expiredRewards.push(reward); + } + } + + return expiredRewards; + } + + /** + * ๆฃ€ๆŸฅ็”จๆˆท็š„ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ็Šถๆ€ + */ + checkUserPendingRewards( + pendingRewards: RewardLedgerEntry[], + ): { + expired: RewardLedgerEntry[]; + stillPending: RewardLedgerEntry[]; + } { + const expired: RewardLedgerEntry[] = []; + const stillPending: RewardLedgerEntry[] = []; + + for (const reward of pendingRewards) { + if (reward.isExpiredNow()) { + reward.expire(); + expired.push(reward); + } else { + stillPending.push(reward); + } + } + + return { expired, stillPending }; + } +} diff --git a/backend/services/reward-service/src/domain/value-objects/hashpower.spec.ts b/backend/services/reward-service/src/domain/value-objects/hashpower.spec.ts new file mode 100644 index 00000000..5bdcd57d --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/hashpower.spec.ts @@ -0,0 +1,76 @@ +import { Hashpower } from './hashpower.vo'; + +describe('Hashpower', () => { + describe('create', () => { + it('should create Hashpower with value', () => { + const hp = Hashpower.create(100); + expect(hp.value).toBe(100); + }); + }); + + describe('zero', () => { + it('should create zero Hashpower', () => { + const hp = Hashpower.zero(); + expect(hp.value).toBe(0); + expect(hp.isZero()).toBe(true); + }); + }); + + describe('fromTreeCount', () => { + it('should calculate hashpower from tree count and percent', () => { + // 10 trees at 2% = 20 hashpower + const hp = Hashpower.fromTreeCount(10, 2); + expect(hp.value).toBe(20); + }); + + it('should return zero for zero percent', () => { + const hp = Hashpower.fromTreeCount(10, 0); + expect(hp.value).toBe(0); + }); + }); + + describe('validation', () => { + it('should throw error for negative value', () => { + expect(() => Hashpower.create(-10)).toThrow('็ฎ—ๅŠ›ไธ่ƒฝไธบ่ดŸๆ•ฐ'); + }); + }); + + describe('add', () => { + it('should add two Hashpower values', () => { + const a = Hashpower.create(100); + const b = Hashpower.create(50); + const result = a.add(b); + expect(result.value).toBe(150); + }); + }); + + describe('subtract', () => { + it('should subtract Hashpower values', () => { + const a = Hashpower.create(100); + const b = Hashpower.create(30); + const result = a.subtract(b); + expect(result.value).toBe(70); + }); + + it('should return zero when subtracting larger value', () => { + const a = Hashpower.create(50); + const b = Hashpower.create(100); + const result = a.subtract(b); + expect(result.value).toBe(0); + }); + }); + + describe('equals', () => { + it('should return true for equal values', () => { + const a = Hashpower.create(100); + const b = Hashpower.create(100); + expect(a.equals(b)).toBe(true); + }); + + it('should return false for different values', () => { + const a = Hashpower.create(100); + const b = Hashpower.create(50); + expect(a.equals(b)).toBe(false); + }); + }); +}); diff --git a/backend/services/reward-service/src/domain/value-objects/hashpower.vo.ts b/backend/services/reward-service/src/domain/value-objects/hashpower.vo.ts new file mode 100644 index 00000000..db0f7def --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/hashpower.vo.ts @@ -0,0 +1,40 @@ +export class Hashpower { + private constructor(public readonly value: number) { + if (value < 0) { + throw new Error('็ฎ—ๅŠ›ไธ่ƒฝไธบ่ดŸๆ•ฐ'); + } + } + + static create(value: number): Hashpower { + return new Hashpower(value); + } + + static zero(): Hashpower { + return new Hashpower(0); + } + + /** + * ๆ นๆฎๆ ‘ๆ•ฐ้‡ๅ’Œ็™พๅˆ†ๆฏ”่ฎก็ฎ—็ฎ—ๅŠ› + * @param treeCount ๆ ‘ๆ•ฐ้‡ + * @param percent ็ฎ—ๅŠ›็™พๅˆ†ๆฏ” (1 = 1%) + */ + static fromTreeCount(treeCount: number, percent: number): Hashpower { + return new Hashpower(treeCount * percent); + } + + add(other: Hashpower): Hashpower { + return new Hashpower(this.value + other.value); + } + + subtract(other: Hashpower): Hashpower { + return new Hashpower(Math.max(0, this.value - other.value)); + } + + equals(other: Hashpower): boolean { + return this.value === other.value; + } + + isZero(): boolean { + return this.value === 0; + } +} diff --git a/backend/services/reward-service/src/domain/value-objects/index.ts b/backend/services/reward-service/src/domain/value-objects/index.ts new file mode 100644 index 00000000..a0912a4d --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/index.ts @@ -0,0 +1,5 @@ +export * from './right-type.enum'; +export * from './reward-status.enum'; +export * from './money.vo'; +export * from './hashpower.vo'; +export * from './reward-source.vo'; diff --git a/backend/services/reward-service/src/domain/value-objects/money.spec.ts b/backend/services/reward-service/src/domain/value-objects/money.spec.ts new file mode 100644 index 00000000..2da26e64 --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/money.spec.ts @@ -0,0 +1,86 @@ +import { Money } from './money.vo'; + +describe('Money', () => { + describe('USDT factory', () => { + it('should create Money with USDT currency', () => { + const money = Money.USDT(100); + expect(money.amount).toBe(100); + expect(money.currency).toBe('USDT'); + }); + }); + + describe('zero factory', () => { + it('should create zero Money', () => { + const money = Money.zero(); + expect(money.amount).toBe(0); + expect(money.isZero()).toBe(true); + }); + }); + + describe('validation', () => { + it('should throw error for negative amount', () => { + expect(() => Money.USDT(-100)).toThrow('้‡‘้ขไธ่ƒฝไธบ่ดŸๆ•ฐ'); + }); + }); + + describe('add', () => { + it('should add two Money values', () => { + const a = Money.USDT(100); + const b = Money.USDT(50); + const result = a.add(b); + expect(result.amount).toBe(150); + }); + }); + + describe('subtract', () => { + it('should subtract Money values', () => { + const a = Money.USDT(100); + const b = Money.USDT(30); + const result = a.subtract(b); + expect(result.amount).toBe(70); + }); + + it('should return zero when subtracting larger value', () => { + const a = Money.USDT(50); + const b = Money.USDT(100); + const result = a.subtract(b); + expect(result.amount).toBe(0); + }); + }); + + describe('multiply', () => { + it('should multiply Money by factor', () => { + const money = Money.USDT(100); + const result = money.multiply(3); + expect(result.amount).toBe(300); + }); + }); + + describe('equals', () => { + it('should return true for equal values', () => { + const a = Money.USDT(100); + const b = Money.USDT(100); + expect(a.equals(b)).toBe(true); + }); + + it('should return false for different values', () => { + const a = Money.USDT(100); + const b = Money.USDT(50); + expect(a.equals(b)).toBe(false); + }); + }); + + describe('isGreaterThan', () => { + it('should return true when greater', () => { + const a = Money.USDT(100); + const b = Money.USDT(50); + expect(a.isGreaterThan(b)).toBe(true); + }); + + it('should return false when less or equal', () => { + const a = Money.USDT(50); + const b = Money.USDT(100); + expect(a.isGreaterThan(b)).toBe(false); + }); + }); +}); diff --git a/backend/services/reward-service/src/domain/value-objects/money.vo.ts b/backend/services/reward-service/src/domain/value-objects/money.vo.ts new file mode 100644 index 00000000..8c190910 --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/money.vo.ts @@ -0,0 +1,48 @@ +export class Money { + private constructor( + public readonly amount: number, + public readonly currency: string = 'USDT', + ) { + if (amount < 0) { + throw new Error('้‡‘้ขไธ่ƒฝไธบ่ดŸๆ•ฐ'); + } + } + + static USDT(amount: number): Money { + return new Money(amount, 'USDT'); + } + + static zero(): Money { + return new Money(0, 'USDT'); + } + + add(other: Money): Money { + if (this.currency !== other.currency) { + throw new Error('่ดงๅธ็ฑปๅž‹ไธๅŒน้…'); + } + return new Money(this.amount + other.amount, this.currency); + } + + subtract(other: Money): Money { + if (this.currency !== other.currency) { + throw new Error('่ดงๅธ็ฑปๅž‹ไธๅŒน้…'); + } + return new Money(Math.max(0, this.amount - other.amount), this.currency); + } + + multiply(factor: number): Money { + return new Money(this.amount * factor, this.currency); + } + + equals(other: Money): boolean { + return this.amount === other.amount && this.currency === other.currency; + } + + isZero(): boolean { + return this.amount === 0; + } + + isGreaterThan(other: Money): boolean { + return this.amount > other.amount; + } +} diff --git a/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts b/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts new file mode 100644 index 00000000..ce7c820a --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts @@ -0,0 +1,25 @@ +import { RightType } from './right-type.enum'; + +export class RewardSource { + private constructor( + public readonly rightType: RightType, + public readonly sourceOrderId: bigint, + public readonly sourceUserId: bigint, + ) {} + + static create( + rightType: RightType, + sourceOrderId: bigint, + sourceUserId: bigint, + ): RewardSource { + return new RewardSource(rightType, sourceOrderId, sourceUserId); + } + + equals(other: RewardSource): boolean { + return ( + this.rightType === other.rightType && + this.sourceOrderId === other.sourceOrderId && + this.sourceUserId === other.sourceUserId + ); + } +} diff --git a/backend/services/reward-service/src/domain/value-objects/reward-status.enum.ts b/backend/services/reward-service/src/domain/value-objects/reward-status.enum.ts new file mode 100644 index 00000000..8b56d366 --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/reward-status.enum.ts @@ -0,0 +1,6 @@ +export enum RewardStatus { + PENDING = 'PENDING', // ๅพ…้ข†ๅ–(24hๅ€’่ฎกๆ—ถ) + SETTLEABLE = 'SETTLEABLE', // ๅฏ็ป“็ฎ— + SETTLED = 'SETTLED', // ๅทฒ็ป“็ฎ— + EXPIRED = 'EXPIRED', // ๅทฒ่ฟ‡ๆœŸ(่ฟ›ๆ€ป้ƒจ็คพๅŒบ) +} diff --git a/backend/services/reward-service/src/domain/value-objects/right-type.enum.ts b/backend/services/reward-service/src/domain/value-objects/right-type.enum.ts new file mode 100644 index 00000000..7d990342 --- /dev/null +++ b/backend/services/reward-service/src/domain/value-objects/right-type.enum.ts @@ -0,0 +1,18 @@ +export enum RightType { + SHARE_RIGHT = 'SHARE_RIGHT', // ๅˆ†ไบซๆƒ็›Š 500U + PROVINCE_AREA_RIGHT = 'PROVINCE_AREA_RIGHT', // ็œๅŒบๅŸŸๆƒ็›Š 15U + 1%็ฎ—ๅŠ› + PROVINCE_TEAM_RIGHT = 'PROVINCE_TEAM_RIGHT', // ็œๅ›ข้˜Ÿๆƒ็›Š 20U + CITY_AREA_RIGHT = 'CITY_AREA_RIGHT', // ๅธ‚ๅŒบๅŸŸๆƒ็›Š 35U + 2%็ฎ—ๅŠ› + CITY_TEAM_RIGHT = 'CITY_TEAM_RIGHT', // ๅธ‚ๅ›ข้˜Ÿๆƒ็›Š 40U + COMMUNITY_RIGHT = 'COMMUNITY_RIGHT', // ็คพๅŒบๆƒ็›Š 80U +} + +// ๆƒ็›Š้‡‘้ข้…็ฝฎ +export const RIGHT_AMOUNTS: Record = { + [RightType.SHARE_RIGHT]: { usdt: 500, hashpowerPercent: 0 }, + [RightType.PROVINCE_AREA_RIGHT]: { usdt: 15, hashpowerPercent: 1 }, + [RightType.PROVINCE_TEAM_RIGHT]: { usdt: 20, hashpowerPercent: 0 }, + [RightType.CITY_AREA_RIGHT]: { usdt: 35, hashpowerPercent: 2 }, + [RightType.CITY_TEAM_RIGHT]: { usdt: 40, hashpowerPercent: 0 }, + [RightType.COMMUNITY_RIGHT]: { usdt: 80, hashpowerPercent: 0 }, +}; diff --git a/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts b/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts new file mode 100644 index 00000000..1d2d0f65 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts @@ -0,0 +1,70 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { IAuthorizationServiceClient } from '../../../domain/services/reward-calculation.service'; + +@Injectable() +export class AuthorizationServiceClient implements IAuthorizationServiceClient { + private readonly logger = new Logger(AuthorizationServiceClient.name); + private readonly baseUrl: string; + + constructor(private readonly configService: ConfigService) { + this.baseUrl = this.configService.get('AUTHORIZATION_SERVICE_URL', 'http://localhost:3006'); + } + + async findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise { + try { + const response = await fetch( + `${this.baseUrl}/authorization/nearest-province?userId=${userId}&provinceCode=${provinceCode}`, + ); + + if (!response.ok) { + this.logger.warn(`No authorized province found for user ${userId}, province ${provinceCode}`); + return null; + } + + const data = await response.json(); + return data.userId ? BigInt(data.userId) : null; + } catch (error) { + this.logger.error(`Error finding nearest authorized province:`, error); + return null; + } + } + + async findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise { + try { + const response = await fetch( + `${this.baseUrl}/authorization/nearest-city?userId=${userId}&cityCode=${cityCode}`, + ); + + if (!response.ok) { + this.logger.warn(`No authorized city found for user ${userId}, city ${cityCode}`); + return null; + } + + const data = await response.json(); + return data.userId ? BigInt(data.userId) : null; + } catch (error) { + this.logger.error(`Error finding nearest authorized city:`, error); + return null; + } + } + + async findNearestCommunity(userId: bigint): Promise { + try { + const response = await fetch( + `${this.baseUrl}/authorization/nearest-community?userId=${userId}`, + ); + + if (!response.ok) { + this.logger.warn(`No community found for user ${userId}`); + return null; + } + + const data = await response.json(); + return data.userId ? BigInt(data.userId) : null; + } catch (error) { + this.logger.error(`Error finding nearest community:`, error); + return null; + } + } +} diff --git a/backend/services/reward-service/src/infrastructure/external/referral-service/referral-service.client.ts b/backend/services/reward-service/src/infrastructure/external/referral-service/referral-service.client.ts new file mode 100644 index 00000000..79084057 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/external/referral-service/referral-service.client.ts @@ -0,0 +1,38 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { IReferralServiceClient } from '../../../domain/services/reward-calculation.service'; + +@Injectable() +export class ReferralServiceClient implements IReferralServiceClient { + private readonly logger = new Logger(ReferralServiceClient.name); + private readonly baseUrl: string; + + constructor(private readonly configService: ConfigService) { + this.baseUrl = this.configService.get('REFERRAL_SERVICE_URL', 'http://localhost:3004'); + } + + async getReferralChain(userId: bigint): Promise<{ + ancestors: Array<{ userId: bigint; hasPlanted: boolean }>; + }> { + try { + const response = await fetch(`${this.baseUrl}/referral/chain/${userId}`); + + if (!response.ok) { + this.logger.warn(`Failed to get referral chain for user ${userId}: ${response.status}`); + return { ancestors: [] }; + } + + const data = await response.json(); + + return { + ancestors: (data.ancestors || []).map((a: any) => ({ + userId: BigInt(a.userId), + hasPlanted: a.hasPlanted ?? false, + })), + }; + } catch (error) { + this.logger.error(`Error fetching referral chain for user ${userId}:`, error); + return { ancestors: [] }; + } + } +} diff --git a/backend/services/reward-service/src/infrastructure/external/wallet-service/wallet-service.client.ts b/backend/services/reward-service/src/infrastructure/external/wallet-service/wallet-service.client.ts new file mode 100644 index 00000000..9be7554a --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/external/wallet-service/wallet-service.client.ts @@ -0,0 +1,80 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +export interface SwapResult { + success: boolean; + txHash?: string; + receivedAmount?: number; + swapRate?: number; + error?: string; +} + +@Injectable() +export class WalletServiceClient { + private readonly logger = new Logger(WalletServiceClient.name); + private readonly baseUrl: string; + + constructor(private readonly configService: ConfigService) { + this.baseUrl = this.configService.get('WALLET_SERVICE_URL', 'http://localhost:3002'); + } + + async executeSwap(params: { + userId: bigint; + usdtAmount: number; + targetCurrency: string; // BNB/OG/USDT/DST + }): Promise { + try { + const response = await fetch(`${this.baseUrl}/wallet/swap`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userId: params.userId.toString(), + usdtAmount: params.usdtAmount, + targetCurrency: params.targetCurrency, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return { + success: false, + error: errorData.message || `Swap failed with status ${response.status}`, + }; + } + + const data = await response.json(); + return { + success: true, + txHash: data.txHash, + receivedAmount: data.receivedAmount, + swapRate: data.swapRate, + }; + } catch (error) { + this.logger.error(`Error executing swap:`, error); + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } + } + + async getSwapRate(fromCurrency: string, toCurrency: string): Promise { + try { + const response = await fetch( + `${this.baseUrl}/wallet/swap-rate?from=${fromCurrency}&to=${toCurrency}`, + ); + + if (!response.ok) { + return null; + } + + const data = await response.json(); + return data.rate; + } catch (error) { + this.logger.error(`Error getting swap rate:`, error); + return null; + } + } +} diff --git a/backend/services/reward-service/src/infrastructure/infrastructure.module.ts b/backend/services/reward-service/src/infrastructure/infrastructure.module.ts new file mode 100644 index 00000000..67cbebca --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/infrastructure.module.ts @@ -0,0 +1,48 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PrismaService } from './persistence/prisma/prisma.service'; +import { RewardLedgerEntryRepositoryImpl } from './persistence/repositories/reward-ledger-entry.repository.impl'; +import { RewardSummaryRepositoryImpl } from './persistence/repositories/reward-summary.repository.impl'; +import { ReferralServiceClient } from './external/referral-service/referral-service.client'; +import { AuthorizationServiceClient } from './external/authorization-service/authorization-service.client'; +import { WalletServiceClient } from './external/wallet-service/wallet-service.client'; +import { KafkaModule } from './kafka/kafka.module'; +import { RedisModule } from './redis/redis.module'; +import { REWARD_LEDGER_ENTRY_REPOSITORY } from '../domain/repositories/reward-ledger-entry.repository.interface'; +import { REWARD_SUMMARY_REPOSITORY } from '../domain/repositories/reward-summary.repository.interface'; +import { REFERRAL_SERVICE_CLIENT, AUTHORIZATION_SERVICE_CLIENT } from '../domain/services/reward-calculation.service'; + +@Module({ + imports: [ConfigModule, KafkaModule, RedisModule], + providers: [ + PrismaService, + { + provide: REWARD_LEDGER_ENTRY_REPOSITORY, + useClass: RewardLedgerEntryRepositoryImpl, + }, + { + provide: REWARD_SUMMARY_REPOSITORY, + useClass: RewardSummaryRepositoryImpl, + }, + { + provide: REFERRAL_SERVICE_CLIENT, + useClass: ReferralServiceClient, + }, + { + provide: AUTHORIZATION_SERVICE_CLIENT, + useClass: AuthorizationServiceClient, + }, + WalletServiceClient, + ], + exports: [ + PrismaService, + REWARD_LEDGER_ENTRY_REPOSITORY, + REWARD_SUMMARY_REPOSITORY, + REFERRAL_SERVICE_CLIENT, + AUTHORIZATION_SERVICE_CLIENT, + WalletServiceClient, + KafkaModule, + RedisModule, + ], +}) +export class InfrastructureModule {} diff --git a/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts b/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts new file mode 100644 index 00000000..ccb0c0e8 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Logger } from '@nestjs/common'; +import { MessagePattern, Payload } from '@nestjs/microservices'; +import { RewardApplicationService } from '../../application/services/reward-application.service'; + +interface PlantingOrderPaidEvent { + orderId: string; + userId: string; + treeCount: number; + provinceCode: string; + cityCode: string; + paidAt: string; +} + +@Controller() +export class EventConsumerController { + private readonly logger = new Logger(EventConsumerController.name); + + constructor( + private readonly rewardService: RewardApplicationService, + ) {} + + /** + * ็›‘ๅฌ่ฎค็ง่ฎขๅ•ๆ”ฏไป˜ๆˆๅŠŸไบ‹ไปถ + */ + @MessagePattern('planting.order.paid') + async handlePlantingOrderPaid(@Payload() message: PlantingOrderPaidEvent) { + this.logger.log(`Received planting.order.paid event: ${JSON.stringify(message)}`); + + try { + // 1. ่ฎก็ฎ—ๅนถๅˆ†้…ๅฅ–ๅŠฑ + await this.rewardService.distributeRewards({ + sourceOrderId: BigInt(message.orderId), + sourceUserId: BigInt(message.userId), + treeCount: message.treeCount, + provinceCode: message.provinceCode, + cityCode: message.cityCode, + }); + + // 2. ๆฃ€ๆŸฅ่ฏฅ็”จๆˆทๆ˜ฏๅฆๆœ‰ๅพ…้ข†ๅ–ๅฅ–ๅŠฑ้œ€่ฆ่ฝฌไธบๅฏ็ป“็ฎ— + await this.rewardService.claimPendingRewardsForUser(BigInt(message.userId)); + + this.logger.log(`Successfully processed planting.order.paid for order ${message.orderId}`); + } catch (error) { + this.logger.error(`Error processing planting.order.paid:`, error); + throw error; + } + } +} diff --git a/backend/services/reward-service/src/infrastructure/kafka/event-publisher.service.ts b/backend/services/reward-service/src/infrastructure/kafka/event-publisher.service.ts new file mode 100644 index 00000000..96467d8f --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/kafka/event-publisher.service.ts @@ -0,0 +1,58 @@ +import { Injectable, Inject, Logger } from '@nestjs/common'; +import { ClientKafka } from '@nestjs/microservices'; +import { DomainEvent } from '../../domain/events/domain-event.base'; + +@Injectable() +export class EventPublisherService { + private readonly logger = new Logger(EventPublisherService.name); + + constructor( + @Inject('KAFKA_SERVICE') + private readonly kafkaClient: ClientKafka, + ) {} + + async publish(event: DomainEvent): Promise { + const topic = this.getTopicForEvent(event); + const message = { + key: event.aggregateId, + value: JSON.stringify({ + eventId: event.eventId, + eventType: event.eventType, + aggregateId: event.aggregateId, + aggregateType: event.aggregateType, + occurredAt: event.occurredAt.toISOString(), + version: event.version, + payload: event.toPayload(), + }), + }; + + try { + this.kafkaClient.emit(topic, message); + this.logger.log(`Published event ${event.eventType} to topic ${topic}`); + } catch (error) { + this.logger.error(`Failed to publish event ${event.eventType}:`, error); + throw error; + } + } + + async publishAll(events: DomainEvent[]): Promise { + for (const event of events) { + await this.publish(event); + } + } + + private getTopicForEvent(event: DomainEvent): string { + switch (event.eventType) { + case 'RewardCreated': + return 'reward.created'; + case 'RewardClaimed': + return 'reward.claimed'; + case 'RewardExpired': + return 'reward.expired'; + case 'RewardSettled': + return 'reward.settled'; + default: + return 'reward.events'; + } + } +} diff --git a/backend/services/reward-service/src/infrastructure/kafka/kafka.module.ts b/backend/services/reward-service/src/infrastructure/kafka/kafka.module.ts new file mode 100644 index 00000000..3abf136c --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/kafka/kafka.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { EventPublisherService } from './event-publisher.service'; + +@Module({ + imports: [ + ClientsModule.registerAsync([ + { + name: 'KAFKA_SERVICE', + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + transport: Transport.KAFKA, + options: { + client: { + clientId: configService.get('KAFKA_CLIENT_ID', 'reward-service'), + brokers: configService.get('KAFKA_BROKERS', 'localhost:9092').split(','), + }, + consumer: { + groupId: configService.get('KAFKA_GROUP_ID', 'reward-service-group'), + }, + }, + }), + inject: [ConfigService], + }, + ]), + ], + providers: [EventPublisherService], + exports: [EventPublisherService, ClientsModule], +}) +export class KafkaModule {} diff --git a/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts new file mode 100644 index 00000000..9e72e8fe --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts @@ -0,0 +1,47 @@ +import { RewardLedgerEntry as PrismaRewardLedgerEntry, Prisma } from '@prisma/client'; +import { RewardLedgerEntry } from '../../../domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { RewardSource } from '../../../domain/value-objects/reward-source.vo'; +import { RewardStatus } from '../../../domain/value-objects/reward-status.enum'; +import { RightType } from '../../../domain/value-objects/right-type.enum'; + +export class RewardLedgerEntryMapper { + static toDomain(raw: PrismaRewardLedgerEntry): RewardLedgerEntry { + return RewardLedgerEntry.reconstitute({ + id: raw.id, + userId: raw.userId, + rewardSource: RewardSource.create( + raw.rightType as RightType, + raw.sourceOrderId, + raw.sourceUserId, + ), + usdtAmount: Number(raw.usdtAmount), + hashpowerAmount: Number(raw.hashpowerAmount), + rewardStatus: raw.rewardStatus as RewardStatus, + createdAt: raw.createdAt, + expireAt: raw.expireAt, + claimedAt: raw.claimedAt, + settledAt: raw.settledAt, + expiredAt: raw.expiredAt, + memo: raw.memo || '', + }); + } + + static toPersistence(entry: RewardLedgerEntry) { + return { + id: entry.id || undefined, + userId: entry.userId, + sourceOrderId: entry.rewardSource.sourceOrderId, + sourceUserId: entry.rewardSource.sourceUserId, + rightType: entry.rewardSource.rightType, + usdtAmount: new Prisma.Decimal(entry.usdtAmount.amount), + hashpowerAmount: new Prisma.Decimal(entry.hashpowerAmount.value), + rewardStatus: entry.rewardStatus, + createdAt: entry.createdAt, + expireAt: entry.expireAt, + claimedAt: entry.claimedAt, + settledAt: entry.settledAt, + expiredAt: entry.expiredAt, + memo: entry.memo, + }; + } +} diff --git a/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-summary.mapper.ts b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-summary.mapper.ts new file mode 100644 index 00000000..48522b52 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-summary.mapper.ts @@ -0,0 +1,40 @@ +import { RewardSummary as PrismaRewardSummary, Prisma } from '@prisma/client'; +import { RewardSummary } from '../../../domain/aggregates/reward-summary/reward-summary.aggregate'; + +export class RewardSummaryMapper { + static toDomain(raw: PrismaRewardSummary): RewardSummary { + return RewardSummary.reconstitute({ + id: raw.id, + userId: raw.userId, + pendingUsdt: Number(raw.pendingUsdt), + pendingHashpower: Number(raw.pendingHashpower), + pendingExpireAt: raw.pendingExpireAt, + settleableUsdt: Number(raw.settleableUsdt), + settleableHashpower: Number(raw.settleableHashpower), + settledTotalUsdt: Number(raw.settledTotalUsdt), + settledTotalHashpower: Number(raw.settledTotalHashpower), + expiredTotalUsdt: Number(raw.expiredTotalUsdt), + expiredTotalHashpower: Number(raw.expiredTotalHashpower), + lastUpdateAt: raw.lastUpdateAt, + createdAt: raw.createdAt, + }); + } + + static toPersistence(summary: RewardSummary) { + return { + id: summary.id || undefined, + userId: summary.userId, + pendingUsdt: new Prisma.Decimal(summary.pendingUsdt.amount), + pendingHashpower: new Prisma.Decimal(summary.pendingHashpower.value), + pendingExpireAt: summary.pendingExpireAt, + settleableUsdt: new Prisma.Decimal(summary.settleableUsdt.amount), + settleableHashpower: new Prisma.Decimal(summary.settleableHashpower.value), + settledTotalUsdt: new Prisma.Decimal(summary.settledTotalUsdt.amount), + settledTotalHashpower: new Prisma.Decimal(summary.settledTotalHashpower.value), + expiredTotalUsdt: new Prisma.Decimal(summary.expiredTotalUsdt.amount), + expiredTotalHashpower: new Prisma.Decimal(summary.expiredTotalHashpower.value), + lastUpdateAt: summary.lastUpdateAt, + createdAt: summary.createdAt, + }; + } +} diff --git a/backend/services/reward-service/src/infrastructure/persistence/prisma/prisma.service.ts b/backend/services/reward-service/src/infrastructure/persistence/prisma/prisma.service.ts new file mode 100644 index 00000000..bb6565f3 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/persistence/prisma/prisma.service.ts @@ -0,0 +1,13 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } +} diff --git a/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts new file mode 100644 index 00000000..a6b494aa --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts @@ -0,0 +1,155 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { RewardLedgerEntryMapper } from '../mappers/reward-ledger-entry.mapper'; +import { RewardLedgerEntry } from '../../../domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { IRewardLedgerEntryRepository } from '../../../domain/repositories/reward-ledger-entry.repository.interface'; +import { RewardStatus } from '../../../domain/value-objects/reward-status.enum'; +import { RightType } from '../../../domain/value-objects/right-type.enum'; + +@Injectable() +export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryRepository { + constructor(private readonly prisma: PrismaService) {} + + async save(entry: RewardLedgerEntry): Promise { + const data = RewardLedgerEntryMapper.toPersistence(entry); + + if (entry.id) { + await this.prisma.rewardLedgerEntry.update({ + where: { id: entry.id }, + data: { + rewardStatus: data.rewardStatus, + expireAt: data.expireAt, + claimedAt: data.claimedAt, + settledAt: data.settledAt, + expiredAt: data.expiredAt, + memo: data.memo, + }, + }); + } else { + const created = await this.prisma.rewardLedgerEntry.create({ + data: { + userId: data.userId, + sourceOrderId: data.sourceOrderId, + sourceUserId: data.sourceUserId, + rightType: data.rightType, + usdtAmount: data.usdtAmount, + hashpowerAmount: data.hashpowerAmount, + rewardStatus: data.rewardStatus, + expireAt: data.expireAt, + claimedAt: data.claimedAt, + settledAt: data.settledAt, + expiredAt: data.expiredAt, + memo: data.memo, + }, + }); + entry.setId(created.id); + } + } + + async saveAll(entries: RewardLedgerEntry[]): Promise { + for (const entry of entries) { + await this.save(entry); + } + } + + async findById(entryId: bigint): Promise { + const raw = await this.prisma.rewardLedgerEntry.findUnique({ + where: { id: entryId }, + }); + return raw ? RewardLedgerEntryMapper.toDomain(raw) : null; + } + + async findByUserId( + userId: bigint, + filters?: { + status?: RewardStatus; + rightType?: RightType; + startDate?: Date; + endDate?: Date; + }, + pagination?: { page: number; pageSize: number }, + ): Promise { + const where: any = { userId }; + + if (filters?.status) { + where.rewardStatus = filters.status; + } + if (filters?.rightType) { + where.rightType = filters.rightType; + } + if (filters?.startDate || filters?.endDate) { + where.createdAt = {}; + if (filters.startDate) { + where.createdAt.gte = filters.startDate; + } + if (filters.endDate) { + where.createdAt.lte = filters.endDate; + } + } + + const skip = pagination ? (pagination.page - 1) * pagination.pageSize : undefined; + const take = pagination?.pageSize; + + const rawList = await this.prisma.rewardLedgerEntry.findMany({ + where, + orderBy: { createdAt: 'desc' }, + skip, + take, + }); + + return rawList.map(RewardLedgerEntryMapper.toDomain); + } + + async findPendingByUserId(userId: bigint): Promise { + const rawList = await this.prisma.rewardLedgerEntry.findMany({ + where: { + userId, + rewardStatus: RewardStatus.PENDING, + }, + orderBy: { createdAt: 'desc' }, + }); + + return rawList.map(RewardLedgerEntryMapper.toDomain); + } + + async findSettleableByUserId(userId: bigint): Promise { + const rawList = await this.prisma.rewardLedgerEntry.findMany({ + where: { + userId, + rewardStatus: RewardStatus.SETTLEABLE, + }, + orderBy: { createdAt: 'desc' }, + }); + + return rawList.map(RewardLedgerEntryMapper.toDomain); + } + + async findExpiredPending(beforeDate: Date): Promise { + const rawList = await this.prisma.rewardLedgerEntry.findMany({ + where: { + rewardStatus: RewardStatus.PENDING, + expireAt: { + lte: beforeDate, + }, + }, + }); + + return rawList.map(RewardLedgerEntryMapper.toDomain); + } + + async findBySourceOrderId(sourceOrderId: bigint): Promise { + const rawList = await this.prisma.rewardLedgerEntry.findMany({ + where: { sourceOrderId }, + }); + + return rawList.map(RewardLedgerEntryMapper.toDomain); + } + + async countByUserId(userId: bigint, status?: RewardStatus): Promise { + const where: any = { userId }; + if (status) { + where.rewardStatus = status; + } + return this.prisma.rewardLedgerEntry.count({ where }); + } +} diff --git a/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-summary.repository.impl.ts b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-summary.repository.impl.ts new file mode 100644 index 00000000..5aeb0244 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-summary.repository.impl.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { RewardSummaryMapper } from '../mappers/reward-summary.mapper'; +import { RewardSummary } from '../../../domain/aggregates/reward-summary/reward-summary.aggregate'; +import { IRewardSummaryRepository } from '../../../domain/repositories/reward-summary.repository.interface'; + +@Injectable() +export class RewardSummaryRepositoryImpl implements IRewardSummaryRepository { + constructor(private readonly prisma: PrismaService) {} + + async save(summary: RewardSummary): Promise { + const data = RewardSummaryMapper.toPersistence(summary); + + if (summary.id) { + await this.prisma.rewardSummary.update({ + where: { id: summary.id }, + data: { + pendingUsdt: data.pendingUsdt, + pendingHashpower: data.pendingHashpower, + pendingExpireAt: data.pendingExpireAt, + settleableUsdt: data.settleableUsdt, + settleableHashpower: data.settleableHashpower, + settledTotalUsdt: data.settledTotalUsdt, + settledTotalHashpower: data.settledTotalHashpower, + expiredTotalUsdt: data.expiredTotalUsdt, + expiredTotalHashpower: data.expiredTotalHashpower, + }, + }); + } else { + const created = await this.prisma.rewardSummary.create({ + data: { + userId: data.userId, + pendingUsdt: data.pendingUsdt, + pendingHashpower: data.pendingHashpower, + pendingExpireAt: data.pendingExpireAt, + settleableUsdt: data.settleableUsdt, + settleableHashpower: data.settleableHashpower, + settledTotalUsdt: data.settledTotalUsdt, + settledTotalHashpower: data.settledTotalHashpower, + expiredTotalUsdt: data.expiredTotalUsdt, + expiredTotalHashpower: data.expiredTotalHashpower, + }, + }); + summary.setId(created.id); + } + } + + async findByUserId(userId: bigint): Promise { + const raw = await this.prisma.rewardSummary.findUnique({ + where: { userId }, + }); + return raw ? RewardSummaryMapper.toDomain(raw) : null; + } + + async getOrCreate(userId: bigint): Promise { + const existing = await this.findByUserId(userId); + if (existing) { + return existing; + } + + const newSummary = RewardSummary.create(userId); + await this.save(newSummary); + return newSummary; + } + + async findByUserIds(userIds: bigint[]): Promise> { + const rawList = await this.prisma.rewardSummary.findMany({ + where: { + userId: { in: userIds }, + }, + }); + + const result = new Map(); + for (const raw of rawList) { + result.set(raw.userId.toString(), RewardSummaryMapper.toDomain(raw)); + } + return result; + } + + async findTopSettleableUsers(limit: number): Promise { + const rawList = await this.prisma.rewardSummary.findMany({ + where: { + settleableUsdt: { gt: 0 }, + }, + orderBy: { settleableUsdt: 'desc' }, + take: limit, + }); + + return rawList.map(RewardSummaryMapper.toDomain); + } +} diff --git a/backend/services/reward-service/src/infrastructure/redis/redis.module.ts b/backend/services/reward-service/src/infrastructure/redis/redis.module.ts new file mode 100644 index 00000000..c1c57b86 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/redis/redis.module.ts @@ -0,0 +1,19 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { RedisService } from './redis.service'; + +@Global() +@Module({ + imports: [ConfigModule], + providers: [ + { + provide: RedisService, + useFactory: (configService: ConfigService) => { + return new RedisService(configService); + }, + inject: [ConfigService], + }, + ], + exports: [RedisService], +}) +export class RedisModule {} diff --git a/backend/services/reward-service/src/infrastructure/redis/redis.service.ts b/backend/services/reward-service/src/infrastructure/redis/redis.service.ts new file mode 100644 index 00000000..e3f72ce6 --- /dev/null +++ b/backend/services/reward-service/src/infrastructure/redis/redis.service.ts @@ -0,0 +1,64 @@ +import { Injectable, OnModuleDestroy, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import Redis from 'ioredis'; + +@Injectable() +export class RedisService implements OnModuleDestroy { + private readonly logger = new Logger(RedisService.name); + private readonly client: Redis; + + constructor(private readonly configService: ConfigService) { + const password = this.configService.get('REDIS_PASSWORD'); + this.client = new Redis({ + host: this.configService.get('REDIS_HOST') || 'localhost', + port: this.configService.get('REDIS_PORT') || 6379, + password: password || undefined, + }); + + this.client.on('connect', () => { + this.logger.log('Connected to Redis'); + }); + + this.client.on('error', (err) => { + this.logger.error('Redis error:', err); + }); + } + + async onModuleDestroy() { + await this.client.quit(); + } + + getClient(): Redis { + return this.client; + } + + async get(key: string): Promise { + return this.client.get(key); + } + + async set(key: string, value: string, ttlSeconds?: number): Promise { + if (ttlSeconds) { + await this.client.setex(key, ttlSeconds, value); + } else { + await this.client.set(key, value); + } + } + + async del(key: string): Promise { + await this.client.del(key); + } + + async exists(key: string): Promise { + const result = await this.client.exists(key); + return result === 1; + } + + async setJson(key: string, value: any, ttlSeconds?: number): Promise { + await this.set(key, JSON.stringify(value), ttlSeconds); + } + + async getJson(key: string): Promise { + const value = await this.get(key); + return value ? JSON.parse(value) : null; + } +} diff --git a/backend/services/reward-service/src/main.ts b/backend/services/reward-service/src/main.ts new file mode 100644 index 00000000..79da1202 --- /dev/null +++ b/backend/services/reward-service/src/main.ts @@ -0,0 +1,43 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe, Logger } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { ConfigService } from '@nestjs/config'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const logger = new Logger('Bootstrap'); + const app = await NestFactory.create(AppModule); + + const configService = app.get(ConfigService); + const port = configService.get('PORT', 3005); + const appName = configService.get('APP_NAME', 'reward-service'); + + // ๅ…จๅฑ€้ชŒ่ฏ็ฎก้“ + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + + // ่ทจๅŸŸ้…็ฝฎ + app.enableCors(); + + // Swagger ้…็ฝฎ + const config = new DocumentBuilder() + .setTitle('Reward Service API') + .setDescription('RWAๆฆด่Žฒๅฅณ็š‡ๅนณๅฐ - ๆƒ็›Šๅฅ–ๅŠฑๅพฎๆœๅŠก API ๆ–‡ๆกฃ') + .setVersion('1.0') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); + + await app.listen(port); + logger.log(`${appName} is running on port ${port}`); + logger.log(`Swagger documentation available at http://localhost:${port}/api`); +} + +bootstrap(); diff --git a/backend/services/reward-service/src/shared/guards/jwt-auth.guard.ts b/backend/services/reward-service/src/shared/guards/jwt-auth.guard.ts new file mode 100644 index 00000000..b9ed4467 --- /dev/null +++ b/backend/services/reward-service/src/shared/guards/jwt-auth.guard.ts @@ -0,0 +1,16 @@ +import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + canActivate(context: ExecutionContext) { + return super.canActivate(context); + } + + handleRequest(err: any, user: any, info: any) { + if (err || !user) { + throw err || new UnauthorizedException('่ฏทๅ…ˆ็™ปๅฝ•'); + } + return user; + } +} diff --git a/backend/services/reward-service/src/shared/strategies/jwt.strategy.ts b/backend/services/reward-service/src/shared/strategies/jwt.strategy.ts new file mode 100644 index 00000000..5b45dfb5 --- /dev/null +++ b/backend/services/reward-service/src/shared/strategies/jwt.strategy.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private readonly configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET') || 'default-secret-key', + }); + } + + async validate(payload: any) { + return { + sub: payload.sub, + username: payload.username, + roles: payload.roles, + }; + } +} diff --git a/backend/services/reward-service/test/app.e2e-spec.ts b/backend/services/reward-service/test/app.e2e-spec.ts new file mode 100644 index 00000000..07454e77 --- /dev/null +++ b/backend/services/reward-service/test/app.e2e-spec.ts @@ -0,0 +1,318 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import request from 'supertest'; +import { App } from 'supertest/types'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { HealthController } from '../src/api/controllers/health.controller'; +import { RewardController } from '../src/api/controllers/reward.controller'; +import { SettlementController } from '../src/api/controllers/settlement.controller'; +import { RewardApplicationService } from '../src/application/services/reward-application.service'; +import { JwtService } from '@nestjs/jwt'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy } from '../src/shared/strategies/jwt.strategy'; +import { RewardStatus } from '../src/domain/value-objects/reward-status.enum'; +import { RightType } from '../src/domain/value-objects/right-type.enum'; + +describe('Reward Service (e2e)', () => { + let app: INestApplication; + let jwtService: JwtService; + let mockRewardService: any; + + const TEST_JWT_SECRET = 'test-secret-key-for-testing'; + + const createTestToken = (userId: string = '100') => { + return jwtService.sign({ + sub: userId, + username: 'testuser', + roles: ['user'], + }); + }; + + beforeEach(async () => { + mockRewardService = { + getRewardSummary: jest.fn().mockResolvedValue({ + pendingUsdt: 1000, + pendingHashpower: 0.5, + pendingExpireAt: new Date(Date.now() + 12 * 60 * 60 * 1000), + settleableUsdt: 500, + settleableHashpower: 0.2, + settledTotalUsdt: 2000, + settledTotalHashpower: 1.0, + expiredTotalUsdt: 100, + expiredTotalHashpower: 0.1, + }), + getRewardDetails: jest.fn().mockResolvedValue({ + data: [ + { + id: '1', + rightType: RightType.SHARE_RIGHT, + usdtAmount: 500, + hashpowerAmount: 0, + rewardStatus: RewardStatus.PENDING, + createdAt: new Date(), + expireAt: new Date(Date.now() + 24 * 60 * 60 * 1000), + remainingTimeMs: 86400000, + claimedAt: null, + settledAt: null, + expiredAt: null, + memo: 'Test reward', + }, + ], + pagination: { + page: 1, + pageSize: 20, + total: 1, + }, + }), + getPendingRewards: jest.fn().mockResolvedValue([ + { + id: '1', + rightType: RightType.SHARE_RIGHT, + usdtAmount: 500, + hashpowerAmount: 0, + createdAt: new Date(), + expireAt: new Date(Date.now() + 12 * 60 * 60 * 1000), + remainingTimeMs: 43200000, + memo: 'Pending reward', + }, + ]), + settleRewards: jest.fn().mockResolvedValue({ + success: true, + settledUsdtAmount: 500, + receivedAmount: 0.25, + settleCurrency: 'BNB', + txHash: '0x123abc', + }), + }; + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [() => ({ JWT_SECRET: TEST_JWT_SECRET })], + }), + PassportModule, + JwtModule.register({ + secret: TEST_JWT_SECRET, + signOptions: { expiresIn: '1h' }, + }), + ], + controllers: [HealthController, RewardController, SettlementController], + providers: [ + { + provide: RewardApplicationService, + useValue: mockRewardService, + }, + { + provide: JwtStrategy, + useFactory: (configService: ConfigService) => { + return new JwtStrategy({ + get: (key: string) => key === 'JWT_SECRET' ? TEST_JWT_SECRET : undefined, + } as ConfigService); + }, + inject: [ConfigService], + }, + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes(new ValidationPipe({ transform: true })); + await app.init(); + + jwtService = moduleFixture.get(JwtService); + }); + + afterEach(async () => { + await app.close(); + }); + + describe('Health Check', () => { + it('/health (GET) should return healthy status', () => { + return request(app.getHttpServer()) + .get('/health') + .expect(200) + .expect((res) => { + expect(res.body.status).toBe('ok'); + expect(res.body.service).toBe('reward-service'); + expect(res.body.timestamp).toBeDefined(); + }); + }); + }); + + describe('Rewards API', () => { + describe('GET /rewards/summary', () => { + it('should return 401 without auth token', () => { + return request(app.getHttpServer()) + .get('/rewards/summary') + .expect(401); + }); + + it('should return reward summary with valid token', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .get('/rewards/summary') + .set('Authorization', `Bearer ${token}`) + .expect(200) + .expect((res) => { + expect(res.body.pendingUsdt).toBe(1000); + expect(res.body.settleableUsdt).toBe(500); + expect(res.body.settledTotalUsdt).toBe(2000); + expect(res.body.expiredTotalUsdt).toBe(100); + }); + }); + }); + + describe('GET /rewards/details', () => { + it('should return 401 without auth token', () => { + return request(app.getHttpServer()) + .get('/rewards/details') + .expect(401); + }); + + it('should return paginated reward details with valid token', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .get('/rewards/details') + .set('Authorization', `Bearer ${token}`) + .expect(200) + .expect((res) => { + expect(res.body.data).toHaveLength(1); + expect(res.body.data[0].usdtAmount).toBe(500); + expect(res.body.pagination.total).toBe(1); + }); + }); + + it('should accept filter parameters', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .get('/rewards/details') + .query({ status: RewardStatus.PENDING, page: 1, pageSize: 10 }) + .set('Authorization', `Bearer ${token}`) + .expect(200); + }); + }); + + describe('GET /rewards/pending', () => { + it('should return 401 without auth token', () => { + return request(app.getHttpServer()) + .get('/rewards/pending') + .expect(401); + }); + + it('should return pending rewards with countdown', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .get('/rewards/pending') + .set('Authorization', `Bearer ${token}`) + .expect(200) + .expect((res) => { + expect(res.body).toHaveLength(1); + expect(res.body[0].usdtAmount).toBe(500); + expect(res.body[0].remainingTimeMs).toBeGreaterThan(0); + }); + }); + }); + }); + + describe('Settlement API', () => { + describe('POST /rewards/settle', () => { + it('should return 401 without auth token', () => { + return request(app.getHttpServer()) + .post('/rewards/settle') + .send({ settleCurrency: 'BNB' }) + .expect(401); + }); + + it('should settle rewards successfully with valid token', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: 'BNB' }) + .expect(201) + .expect((res) => { + expect(res.body.success).toBe(true); + expect(res.body.settledUsdtAmount).toBe(500); + expect(res.body.receivedAmount).toBe(0.25); + expect(res.body.settleCurrency).toBe('BNB'); + expect(res.body.txHash).toBe('0x123abc'); + }); + }); + + it('should validate settleCurrency parameter', () => { + const token = createTestToken(); + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: '' }) + .expect(400); + }); + + it('should accept different settlement currencies', async () => { + const token = createTestToken(); + const currencies = ['BNB', 'OG', 'USDT', 'DST']; + + for (const currency of currencies) { + mockRewardService.settleRewards.mockResolvedValueOnce({ + success: true, + settledUsdtAmount: 500, + receivedAmount: currency === 'USDT' ? 500 : 0.25, + settleCurrency: currency, + txHash: '0x123', + }); + + await request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: currency }) + .expect(201); + } + }); + }); + + describe('Settlement failure scenarios', () => { + it('should handle no settleable rewards', () => { + const token = createTestToken(); + mockRewardService.settleRewards.mockResolvedValueOnce({ + success: false, + settledUsdtAmount: 0, + receivedAmount: 0, + settleCurrency: 'BNB', + error: 'ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š', + }); + + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: 'BNB' }) + .expect(201) + .expect((res) => { + expect(res.body.success).toBe(false); + expect(res.body.error).toBe('ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š'); + }); + }); + + it('should handle wallet service failure', () => { + const token = createTestToken(); + mockRewardService.settleRewards.mockResolvedValueOnce({ + success: false, + settledUsdtAmount: 500, + receivedAmount: 0, + settleCurrency: 'BNB', + error: 'Insufficient liquidity', + }); + + return request(app.getHttpServer()) + .post('/rewards/settle') + .set('Authorization', `Bearer ${token}`) + .send({ settleCurrency: 'BNB' }) + .expect(201) + .expect((res) => { + expect(res.body.success).toBe(false); + expect(res.body.error).toBe('Insufficient liquidity'); + }); + }); + }); + }); +}); diff --git a/backend/services/reward-service/test/integration/reward-application.service.spec.ts b/backend/services/reward-service/test/integration/reward-application.service.spec.ts new file mode 100644 index 00000000..cd1f2649 --- /dev/null +++ b/backend/services/reward-service/test/integration/reward-application.service.spec.ts @@ -0,0 +1,354 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RewardApplicationService } from '../../src/application/services/reward-application.service'; +import { RewardCalculationService } from '../../src/domain/services/reward-calculation.service'; +import { RewardExpirationService } from '../../src/domain/services/reward-expiration.service'; +import { REWARD_LEDGER_ENTRY_REPOSITORY } from '../../src/domain/repositories/reward-ledger-entry.repository.interface'; +import { REWARD_SUMMARY_REPOSITORY } from '../../src/domain/repositories/reward-summary.repository.interface'; +import { EventPublisherService } from '../../src/infrastructure/kafka/event-publisher.service'; +import { WalletServiceClient } from '../../src/infrastructure/external/wallet-service/wallet-service.client'; +import { RewardLedgerEntry } from '../../src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate'; +import { RewardSummary } from '../../src/domain/aggregates/reward-summary/reward-summary.aggregate'; +import { RewardSource } from '../../src/domain/value-objects/reward-source.vo'; +import { RightType } from '../../src/domain/value-objects/right-type.enum'; +import { RewardStatus } from '../../src/domain/value-objects/reward-status.enum'; +import { Money } from '../../src/domain/value-objects/money.vo'; +import { Hashpower } from '../../src/domain/value-objects/hashpower.vo'; + +describe('RewardApplicationService (Integration)', () => { + let service: RewardApplicationService; + let mockLedgerRepository: any; + let mockSummaryRepository: any; + let mockEventPublisher: any; + let mockWalletService: any; + let mockCalculationService: any; + + beforeEach(async () => { + mockLedgerRepository = { + save: jest.fn(), + saveAll: jest.fn(), + findPendingByUserId: jest.fn(), + findSettleableByUserId: jest.fn(), + findExpiredPending: jest.fn(), + findByUserId: jest.fn(), + countByUserId: jest.fn(), + }; + + mockSummaryRepository = { + getOrCreate: jest.fn(), + findByUserId: jest.fn(), + save: jest.fn(), + }; + + mockEventPublisher = { + publishAll: jest.fn(), + }; + + mockWalletService = { + executeSwap: jest.fn(), + }; + + mockCalculationService = { + calculateRewards: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + RewardApplicationService, + { + provide: RewardCalculationService, + useValue: mockCalculationService, + }, + { + provide: RewardExpirationService, + useValue: { expireOverdueRewards: jest.fn((r) => r) }, + }, + { + provide: REWARD_LEDGER_ENTRY_REPOSITORY, + useValue: mockLedgerRepository, + }, + { + provide: REWARD_SUMMARY_REPOSITORY, + useValue: mockSummaryRepository, + }, + { + provide: EventPublisherService, + useValue: mockEventPublisher, + }, + { + provide: WalletServiceClient, + useValue: mockWalletService, + }, + ], + }).compile(); + + service = module.get(RewardApplicationService); + }); + + describe('distributeRewards', () => { + it('should distribute rewards and update summaries', async () => { + const params = { + sourceOrderId: BigInt(1), + sourceUserId: BigInt(100), + treeCount: 10, + provinceCode: '440000', + cityCode: '440100', + }; + + const mockReward = RewardLedgerEntry.createPending({ + userId: BigInt(200), + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(100)), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + memo: 'Test', + }); + + const mockSummary = RewardSummary.create(BigInt(200)); + + mockCalculationService.calculateRewards.mockResolvedValue([mockReward]); + mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary); + + await service.distributeRewards(params); + + expect(mockCalculationService.calculateRewards).toHaveBeenCalledWith(params); + expect(mockLedgerRepository.saveAll).toHaveBeenCalledWith([mockReward]); + expect(mockSummaryRepository.save).toHaveBeenCalled(); + expect(mockEventPublisher.publishAll).toHaveBeenCalled(); + }); + }); + + describe('claimPendingRewardsForUser', () => { + it('should claim pending rewards and move to settleable', async () => { + const userId = BigInt(100); + + const pendingReward = RewardLedgerEntry.createPending({ + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + memo: 'Test reward', + }); + + const mockSummary = RewardSummary.create(userId); + mockSummary.addPending( + Money.USDT(500), + Hashpower.zero(), + new Date(Date.now() + 24 * 60 * 60 * 1000), + ); + + mockLedgerRepository.findPendingByUserId.mockResolvedValue([pendingReward]); + mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary); + + const result = await service.claimPendingRewardsForUser(userId); + + expect(result.claimedCount).toBe(1); + expect(result.totalUsdtClaimed).toBe(500); + expect(mockLedgerRepository.save).toHaveBeenCalled(); + expect(mockSummaryRepository.save).toHaveBeenCalled(); + }); + + it('should skip expired rewards', async () => { + const userId = BigInt(100); + + const expiredReward = RewardLedgerEntry.reconstitute({ + id: BigInt(1), + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: 500, + hashpowerAmount: 0, + rewardStatus: RewardStatus.PENDING, + createdAt: new Date(Date.now() - 25 * 60 * 60 * 1000), + expireAt: new Date(Date.now() - 1000), + claimedAt: null, + settledAt: null, + expiredAt: null, + memo: 'Expired reward', + }); + + const mockSummary = RewardSummary.create(userId); + + mockLedgerRepository.findPendingByUserId.mockResolvedValue([expiredReward]); + mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary); + + const result = await service.claimPendingRewardsForUser(userId); + + expect(result.claimedCount).toBe(0); + expect(result.totalUsdtClaimed).toBe(0); + }); + }); + + describe('settleRewards', () => { + it('should settle rewards and call wallet service', async () => { + const userId = BigInt(100); + + const settleableReward = RewardLedgerEntry.reconstitute({ + id: BigInt(1), + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: 500, + hashpowerAmount: 0, + rewardStatus: RewardStatus.SETTLEABLE, + createdAt: new Date(), + expireAt: null, + claimedAt: new Date(), + settledAt: null, + expiredAt: null, + memo: 'Test', + }); + + const mockSummary = RewardSummary.create(userId); + mockSummary.addSettleable(Money.USDT(500), Hashpower.zero()); + + mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]); + mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary); + mockWalletService.executeSwap.mockResolvedValue({ + success: true, + receivedAmount: 0.25, + txHash: '0x123', + }); + + const result = await service.settleRewards({ + userId, + settleCurrency: 'BNB', + }); + + expect(result.success).toBe(true); + expect(result.settledUsdtAmount).toBe(500); + expect(result.receivedAmount).toBe(0.25); + expect(result.settleCurrency).toBe('BNB'); + expect(mockWalletService.executeSwap).toHaveBeenCalledWith({ + userId, + usdtAmount: 500, + targetCurrency: 'BNB', + }); + }); + + it('should return error when no settleable rewards', async () => { + const userId = BigInt(100); + + mockLedgerRepository.findSettleableByUserId.mockResolvedValue([]); + + const result = await service.settleRewards({ + userId, + settleCurrency: 'BNB', + }); + + expect(result.success).toBe(false); + expect(result.error).toBe('ๆฒกๆœ‰ๅฏ็ป“็ฎ—็š„ๆ”ถ็›Š'); + }); + + it('should handle wallet service failure', async () => { + const userId = BigInt(100); + + const settleableReward = RewardLedgerEntry.reconstitute({ + id: BigInt(1), + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: 500, + hashpowerAmount: 0, + rewardStatus: RewardStatus.SETTLEABLE, + createdAt: new Date(), + expireAt: null, + claimedAt: new Date(), + settledAt: null, + expiredAt: null, + memo: 'Test', + }); + + const mockSummary = RewardSummary.create(userId); + mockSummary.addSettleable(Money.USDT(500), Hashpower.zero()); + + mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]); + mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary); + mockWalletService.executeSwap.mockResolvedValue({ + success: false, + error: 'Insufficient liquidity', + }); + + const result = await service.settleRewards({ + userId, + settleCurrency: 'BNB', + }); + + expect(result.success).toBe(false); + expect(result.error).toBe('Insufficient liquidity'); + }); + }); + + describe('getRewardSummary', () => { + it('should return reward summary for user', async () => { + const userId = BigInt(100); + const mockSummary = RewardSummary.create(userId); + + mockSummaryRepository.findByUserId.mockResolvedValue(mockSummary); + + const result = await service.getRewardSummary(userId); + + expect(result.pendingUsdt).toBe(0); + expect(result.settleableUsdt).toBe(0); + expect(result.settledTotalUsdt).toBe(0); + expect(result.expiredTotalUsdt).toBe(0); + }); + + it('should return zero values when no summary exists', async () => { + const userId = BigInt(100); + + mockSummaryRepository.findByUserId.mockResolvedValue(null); + + const result = await service.getRewardSummary(userId); + + expect(result.pendingUsdt).toBe(0); + expect(result.settleableUsdt).toBe(0); + }); + }); + + describe('getRewardDetails', () => { + it('should return paginated reward details', async () => { + const userId = BigInt(100); + const mockReward = RewardLedgerEntry.reconstitute({ + id: BigInt(1), + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: 500, + hashpowerAmount: 0, + rewardStatus: RewardStatus.PENDING, + createdAt: new Date(), + expireAt: new Date(Date.now() + 24 * 60 * 60 * 1000), + claimedAt: null, + settledAt: null, + expiredAt: null, + memo: 'Test', + }); + + mockLedgerRepository.findByUserId.mockResolvedValue([mockReward]); + mockLedgerRepository.countByUserId.mockResolvedValue(1); + + const result = await service.getRewardDetails(userId, {}, { page: 1, pageSize: 20 }); + + expect(result.data).toHaveLength(1); + expect(result.data[0].usdtAmount).toBe(500); + expect(result.pagination.total).toBe(1); + }); + }); + + describe('getPendingRewards', () => { + it('should return pending rewards with countdown', async () => { + const userId = BigInt(100); + + const pendingReward = RewardLedgerEntry.createPending({ + userId, + rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)), + usdtAmount: Money.USDT(500), + hashpowerAmount: Hashpower.zero(), + memo: 'Test', + }); + + mockLedgerRepository.findPendingByUserId.mockResolvedValue([pendingReward]); + + const result = await service.getPendingRewards(userId); + + expect(result).toHaveLength(1); + expect(result[0].usdtAmount).toBe(500); + expect(result[0].remainingTimeMs).toBeGreaterThan(0); + }); + }); +}); diff --git a/backend/services/reward-service/test/integration/reward-calculation.service.spec.ts b/backend/services/reward-service/test/integration/reward-calculation.service.spec.ts new file mode 100644 index 00000000..6a1c3b48 --- /dev/null +++ b/backend/services/reward-service/test/integration/reward-calculation.service.spec.ts @@ -0,0 +1,233 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { + RewardCalculationService, + REFERRAL_SERVICE_CLIENT, + AUTHORIZATION_SERVICE_CLIENT, +} from '../../src/domain/services/reward-calculation.service'; +import { RightType } from '../../src/domain/value-objects/right-type.enum'; + +describe('RewardCalculationService (Integration)', () => { + let service: RewardCalculationService; + let mockReferralService: any; + let mockAuthorizationService: any; + + beforeEach(async () => { + mockReferralService = { + getReferralChain: jest.fn(), + }; + + mockAuthorizationService = { + findNearestAuthorizedProvince: jest.fn(), + findNearestAuthorizedCity: jest.fn(), + findNearestCommunity: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + RewardCalculationService, + { + provide: REFERRAL_SERVICE_CLIENT, + useValue: mockReferralService, + }, + { + provide: AUTHORIZATION_SERVICE_CLIENT, + useValue: mockAuthorizationService, + }, + ], + }).compile(); + + service = module.get(RewardCalculationService); + }); + + describe('calculateRewards', () => { + const baseParams = { + sourceOrderId: BigInt(1), + sourceUserId: BigInt(100), + treeCount: 10, + provinceCode: '440000', + cityCode: '440100', + }; + + it('should calculate all 6 types of rewards', async () => { + // Setup: referrer with planting status + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: true }], + }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300)); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400)); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500)); + + const rewards = await service.calculateRewards(baseParams); + + // Should return 6 rewards (one for each type) + expect(rewards).toHaveLength(6); + + // Verify each reward type exists + const rightTypes = rewards.map((r) => r.rewardSource.rightType); + expect(rightTypes).toContain(RightType.SHARE_RIGHT); + expect(rightTypes).toContain(RightType.PROVINCE_TEAM_RIGHT); + expect(rightTypes).toContain(RightType.PROVINCE_AREA_RIGHT); + expect(rightTypes).toContain(RightType.CITY_TEAM_RIGHT); + expect(rightTypes).toContain(RightType.CITY_AREA_RIGHT); + expect(rightTypes).toContain(RightType.COMMUNITY_RIGHT); + }); + + it('should calculate share right reward (500 USDT) when referrer has planted', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: true }], + }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const shareReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT, + ); + expect(shareReward).toBeDefined(); + expect(shareReward?.usdtAmount.amount).toBe(500 * 10); // 500 USDT per tree + expect(shareReward?.userId).toBe(BigInt(200)); + expect(shareReward?.isSettleable).toBe(true); // Already planted, so settleable + }); + + it('should create pending share right reward when referrer has not planted', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [{ userId: BigInt(200), hasPlanted: false }], + }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const shareReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT, + ); + expect(shareReward).toBeDefined(); + expect(shareReward?.isPending).toBe(true); // Not planted, so pending with 24h countdown + expect(shareReward?.expireAt).toBeDefined(); + }); + + it('should assign share right to headquarters when no referrer', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ + ancestors: [], + }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const shareReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT, + ); + expect(shareReward).toBeDefined(); + expect(shareReward?.userId).toBe(BigInt(1)); // Headquarters community ID + }); + + it('should calculate province team right (20 USDT)', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300)); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const provinceTeamReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT, + ); + expect(provinceTeamReward).toBeDefined(); + expect(provinceTeamReward?.usdtAmount.amount).toBe(20 * 10); + expect(provinceTeamReward?.userId).toBe(BigInt(300)); + }); + + it('should calculate province area right (15 USDT + 1% hashpower)', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const provinceAreaReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.PROVINCE_AREA_RIGHT, + ); + expect(provinceAreaReward).toBeDefined(); + expect(provinceAreaReward?.usdtAmount.amount).toBe(15 * 10); + expect(provinceAreaReward?.hashpowerAmount.value).toBe(1 * 10); // 1% * 10 trees = 10 + }); + + it('should calculate city team right (40 USDT)', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400)); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const cityTeamReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT, + ); + expect(cityTeamReward).toBeDefined(); + expect(cityTeamReward?.usdtAmount.amount).toBe(40 * 10); + expect(cityTeamReward?.userId).toBe(BigInt(400)); + }); + + it('should calculate city area right (35 USDT + 2% hashpower)', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + const cityAreaReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.CITY_AREA_RIGHT, + ); + expect(cityAreaReward).toBeDefined(); + expect(cityAreaReward?.usdtAmount.amount).toBe(35 * 10); + expect(cityAreaReward?.hashpowerAmount.value).toBe(2 * 10); // 2% * 10 trees = 20 + }); + + it('should calculate community right (80 USDT)', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500)); + + const rewards = await service.calculateRewards(baseParams); + + const communityReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT, + ); + expect(communityReward).toBeDefined(); + expect(communityReward?.usdtAmount.amount).toBe(80 * 10); + expect(communityReward?.userId).toBe(BigInt(500)); + }); + + it('should assign to headquarters when no authorized holder found', async () => { + mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] }); + mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null); + mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null); + mockAuthorizationService.findNearestCommunity.mockResolvedValue(null); + + const rewards = await service.calculateRewards(baseParams); + + // Province team, city team, community should go to headquarters (ID=1) + const provinceTeamReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT, + ); + const cityTeamReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT, + ); + const communityReward = rewards.find( + (r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT, + ); + + expect(provinceTeamReward?.userId).toBe(BigInt(1)); + expect(cityTeamReward?.userId).toBe(BigInt(1)); + expect(communityReward?.userId).toBe(BigInt(1)); + }); + }); +}); diff --git a/backend/services/reward-service/test/jest-e2e.json b/backend/services/reward-service/test/jest-e2e.json new file mode 100644 index 00000000..e9d912f3 --- /dev/null +++ b/backend/services/reward-service/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/backend/services/reward-service/tsconfig.build.json b/backend/services/reward-service/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/backend/services/reward-service/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/backend/services/reward-service/tsconfig.json b/backend/services/reward-service/tsconfig.json index e69de29b..aba29b0e 100644 --- a/backend/services/reward-service/tsconfig.json +++ b/backend/services/reward-service/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "resolvePackageJsonExports": true, + "esModuleInterop": true, + "isolatedModules": true, + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } +}