From edb79180a187e11c354f21eb2080dc6a6ef75e9b Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Sun, 2 Aug 2026 08:08:11 +0200 Subject: [PATCH] Initial commit: NestJS backend for Child Gold Land Co-authored-by: Cursor --- .env.example | 13 + .gitignore | 5 + README.md | 41 + nest-cli.json | 9 + package-lock.json | 4999 +++++++++++++++++ package.json | 43 + src/achievements/achievements.controller.ts | 13 + src/achievements/achievements.module.ts | 11 + src/achievements/achievements.service.ts | 33 + src/app.module.ts | 35 + src/auth/auth-seed.service.ts | 14 + src/auth/auth.controller.ts | 25 + src/auth/auth.module.ts | 32 + src/auth/auth.service.ts | 205 + src/auth/dto/login.dto.ts | 9 + src/auth/dto/register.dto.ts | 13 + src/challenges/challenges.controller.ts | 24 + src/challenges/challenges.module.ts | 12 + src/challenges/challenges.service.ts | 68 + .../dto/complete-daily-challenge.dto.ts | 7 + .../decorators/current-user.decorator.ts | 9 + src/common/decorators/public.decorator.ts | 4 + src/common/filters/api-exception.filter.ts | 61 + src/common/guards/jwt-auth.guard.ts | 55 + src/common/pipes/strict-body.pipe.ts | 10 + src/common/types/authenticated-request.ts | 5 + src/config/config.module.ts | 13 + src/config/env.validation.ts | 60 + src/database/database.module.ts | 9 + src/database/database.service.ts | 55 + src/database/database.tokens.ts | 1 + src/database/migration.service.ts | 65 + src/games/dto/complete-game.dto.ts | 1 + src/games/games.controller.ts | 25 + src/games/games.module.ts | 12 + src/games/games.service.ts | 57 + src/gold-land/dto/complete-level.dto.ts | 35 + .../exceptions/progression.exception.ts | 10 + src/gold-land/gold-land.controller.ts | 87 + src/gold-land/gold-land.module.ts | 14 + src/gold-land/lessons/gold-market.lesson.ts | 73 + src/gold-land/lessons/inflation.lesson.ts | 73 + src/gold-land/lessons/lesson.grader.ts | 146 + src/gold-land/lessons/lesson.loader.ts | 6 + src/gold-land/lessons/lesson.registry.ts | 39 + src/gold-land/lessons/lesson.sanitizer.ts | 48 + src/gold-land/lessons/lesson.types.ts | 109 + .../lessons/needs-and-wants.lesson.ts | 76 + src/gold-land/lessons/saving.lesson.ts | 73 + src/gold-land/lessons/what-is-gold.lesson.ts | 73 + src/gold-land/lessons/what-is-money.lesson.ts | 73 + src/gold-land/levels/level.types.ts | 75 + src/gold-land/levels/levels.ts | 182 + src/gold-land/services/progression.service.ts | 419 ++ src/gold-land/services/reward.service.ts | 78 + src/health/health.controller.ts | 15 + src/health/health.module.ts | 7 + src/main.ts | 43 + .../dto/update-market-preferences.dto.ts | 9 + src/market/market.controller.ts | 24 + src/market/market.module.ts | 12 + src/market/market.service.ts | 45 + src/progression/levels.ts | 37 + src/progression/progression-sync.service.ts | 180 + src/progression/progression.module.ts | 11 + src/shared/catalog/achievements.constants.ts | 48 + src/shared/catalog/challenge.constants.ts | 79 + src/shared/catalog/default-user-state.ts | 52 + src/shared/catalog/games.constants.ts | 60 + src/shared/catalog/market.constants.ts | 28 + src/shared/catalog/store.constants.ts | 18 + src/shared/economy/user-economy.helpers.ts | 37 + src/shared/types/user.types.ts | 98 + src/store/dto/purchase-store-item.dto.ts | 7 + src/store/store.controller.ts | 24 + src/store/store.module.ts | 12 + src/store/store.service.ts | 53 + src/users/dto/update-password.dto.ts | 11 + src/users/dto/update-profile.dto.ts | 7 + src/users/users.controller.ts | 50 + src/users/users.module.ts | 14 + src/users/users.service.ts | 107 + src/wallet/dto/buy-gold.dto.ts | 6 + src/wallet/dto/sell-gold.dto.ts | 6 + src/wallet/wallet-state.helpers.ts | 28 + src/wallet/wallet.controller.ts | 28 + src/wallet/wallet.module.ts | 12 + src/wallet/wallet.service.ts | 73 + test/integration.test.ts | 2230 ++++++++ test/progression.test.ts | 324 ++ tsconfig.build.json | 4 + tsconfig.build.tsbuildinfo | 1 + tsconfig.json | 23 + 93 files changed, 11565 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 nest-cli.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/achievements/achievements.controller.ts create mode 100644 src/achievements/achievements.module.ts create mode 100644 src/achievements/achievements.service.ts create mode 100644 src/app.module.ts create mode 100644 src/auth/auth-seed.service.ts create mode 100644 src/auth/auth.controller.ts create mode 100644 src/auth/auth.module.ts create mode 100644 src/auth/auth.service.ts create mode 100644 src/auth/dto/login.dto.ts create mode 100644 src/auth/dto/register.dto.ts create mode 100644 src/challenges/challenges.controller.ts create mode 100644 src/challenges/challenges.module.ts create mode 100644 src/challenges/challenges.service.ts create mode 100644 src/challenges/dto/complete-daily-challenge.dto.ts create mode 100644 src/common/decorators/current-user.decorator.ts create mode 100644 src/common/decorators/public.decorator.ts create mode 100644 src/common/filters/api-exception.filter.ts create mode 100644 src/common/guards/jwt-auth.guard.ts create mode 100644 src/common/pipes/strict-body.pipe.ts create mode 100644 src/common/types/authenticated-request.ts create mode 100644 src/config/config.module.ts create mode 100644 src/config/env.validation.ts create mode 100644 src/database/database.module.ts create mode 100644 src/database/database.service.ts create mode 100644 src/database/database.tokens.ts create mode 100644 src/database/migration.service.ts create mode 100644 src/games/dto/complete-game.dto.ts create mode 100644 src/games/games.controller.ts create mode 100644 src/games/games.module.ts create mode 100644 src/games/games.service.ts create mode 100644 src/gold-land/dto/complete-level.dto.ts create mode 100644 src/gold-land/exceptions/progression.exception.ts create mode 100644 src/gold-land/gold-land.controller.ts create mode 100644 src/gold-land/gold-land.module.ts create mode 100644 src/gold-land/lessons/gold-market.lesson.ts create mode 100644 src/gold-land/lessons/inflation.lesson.ts create mode 100644 src/gold-land/lessons/lesson.grader.ts create mode 100644 src/gold-land/lessons/lesson.loader.ts create mode 100644 src/gold-land/lessons/lesson.registry.ts create mode 100644 src/gold-land/lessons/lesson.sanitizer.ts create mode 100644 src/gold-land/lessons/lesson.types.ts create mode 100644 src/gold-land/lessons/needs-and-wants.lesson.ts create mode 100644 src/gold-land/lessons/saving.lesson.ts create mode 100644 src/gold-land/lessons/what-is-gold.lesson.ts create mode 100644 src/gold-land/lessons/what-is-money.lesson.ts create mode 100644 src/gold-land/levels/level.types.ts create mode 100644 src/gold-land/levels/levels.ts create mode 100644 src/gold-land/services/progression.service.ts create mode 100644 src/gold-land/services/reward.service.ts create mode 100644 src/health/health.controller.ts create mode 100644 src/health/health.module.ts create mode 100644 src/main.ts create mode 100644 src/market/dto/update-market-preferences.dto.ts create mode 100644 src/market/market.controller.ts create mode 100644 src/market/market.module.ts create mode 100644 src/market/market.service.ts create mode 100644 src/progression/levels.ts create mode 100644 src/progression/progression-sync.service.ts create mode 100644 src/progression/progression.module.ts create mode 100644 src/shared/catalog/achievements.constants.ts create mode 100644 src/shared/catalog/challenge.constants.ts create mode 100644 src/shared/catalog/default-user-state.ts create mode 100644 src/shared/catalog/games.constants.ts create mode 100644 src/shared/catalog/market.constants.ts create mode 100644 src/shared/catalog/store.constants.ts create mode 100644 src/shared/economy/user-economy.helpers.ts create mode 100644 src/shared/types/user.types.ts create mode 100644 src/store/dto/purchase-store-item.dto.ts create mode 100644 src/store/store.controller.ts create mode 100644 src/store/store.module.ts create mode 100644 src/store/store.service.ts create mode 100644 src/users/dto/update-password.dto.ts create mode 100644 src/users/dto/update-profile.dto.ts create mode 100644 src/users/users.controller.ts create mode 100644 src/users/users.module.ts create mode 100644 src/users/users.service.ts create mode 100644 src/wallet/dto/buy-gold.dto.ts create mode 100644 src/wallet/dto/sell-gold.dto.ts create mode 100644 src/wallet/wallet-state.helpers.ts create mode 100644 src/wallet/wallet.controller.ts create mode 100644 src/wallet/wallet.module.ts create mode 100644 src/wallet/wallet.service.ts create mode 100644 test/integration.test.ts create mode 100644 test/progression.test.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.build.tsbuildinfo create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cab73ba --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Server — production cutover uses PORT=3000 (replaces legacy Fastify) +PORT=3000 +HOST=0.0.0.0 +NODE_ENV=development + +# CORS (Vue dev server) +CORS_ORIGIN=http://localhost:5173 + +# SQLite — production database +# DB_PATH=E:/absolute/path/to/backend-nest/data/app.db + +# JWT — must match legacy Fastify when migrating existing tokens +JWT_SECRET=gold-land-dev-secret-change-in-production diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5a7671 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +data/ +.env +*.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..8247d31 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Child Gold Land — Nest API (primary backend) + +NestJS API for Child Gold Land (primary backend since G2). + +Legacy Fastify `backend/` was removed in G4 (2026-08-01). + +## Requirements + +- Node.js **22+** (`nvm use 22.12.0`) + +## Configuration + +Copy `.env.example` to `.env` or use the checked-in local `.env` (gitignored): + +| Variable | Production cutover | +|----------|-------------------| +| `PORT` | `3000` | +| `DB_PATH` | `backend-nest/data/app.db` | +| `JWT_SECRET` | Same as legacy Fastify | +| `CORS_ORIGIN` | `http://localhost:5173` | + +## Development + +```powershell +npm run dev +``` + +## Production + +```powershell +npm run build +npm run start +``` + +## Tests + +```powershell +npm run type-check +npm run test:progression +npm run test:integration +``` diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..2f068a1 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "tsConfigPath": "tsconfig.build.json" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8363a5c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4999 @@ +{ + "name": "child-gold-land-backend-nest", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "child-gold-land-backend-nest", + "version": "0.0.0", + "dependencies": { + "@nestjs/common": "^11.0.12", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.12", + "@nestjs/jwt": "^11.0.0", + "@nestjs/platform-fastify": "^11.0.12", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^12.2.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.6", + "@nestjs/testing": "^11.0.12", + "@types/bcryptjs": "^2.4.6", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^24.13.3", + "tsx": "^4.20.3", + "typescript": "~5.8.3" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "19.2.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.27.tgz", + "integrity": "sha512-3amNzoCVSKd7ah6l6lBQL4onwwJvqvam7FMoQBILrxtW5LB5ezh8gMSPuA4zJjKjoRzf9uoWdlzqv/84I52xZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "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/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.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.27.tgz", + "integrity": "sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "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.27", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.27.tgz", + "integrity": "sha512-wHYH6SVXVykhLzovUHtYor3Nl4SpIiITi7r9DQDaKYUD4hpRBx25W6N9eGuakT9Vd5tV/x6wmvQFWQZQwFB7eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/cors": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.2.0.tgz", + "integrity": "sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/@fastify/cors/node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz", + "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/formbody": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@fastify/formbody/-/formbody-8.0.2.tgz", + "integrity": "sha512-84v5J2KrkXzjgBpYnaNRPqwgMsmY7ZDjuj0YVuMR3NXCJRCgKEZy/taSP1wUYGn0onfxJpLyRGDLa+NMaDJtnA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-querystring": "^1.1.2", + "fastify-plugin": "^5.0.0" + } + }, + "node_modules/@fastify/formbody/node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.2.tgz", + "integrity": "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "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/@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/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/@nestjs/cli": { + "version": "11.0.24", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.24.tgz", + "integrity": "sha512-aIHxQLSYtXShifA3zwWIeznEsZnNa3Iz2QRykFj+sl9IcbERBHr5nH87FRgywM+He3NxoF5WazHfR8FsmVeWxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.27", + "@angular-devkit/schematics": "19.2.27", + "@angular-devkit/schematics-cli": "19.2.27", + "@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.6", + "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.106.2", + "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 || ^0.8.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.28.tgz", + "integrity": "sha512-bRImsxibie+AM7xjdwcrm/gr5YeacI65kSBNzTufa1Ib5iwziaY/lqMtRh9THq6pbV4e1HP9aI2ZxGUumnmaoQ==", + "license": "MIT", + "dependencies": { + "file-type": "21.3.4", + "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.4", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.4.tgz", + "integrity": "sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==", + "license": "MIT", + "dependencies": { + "dotenv": "17.4.1", + "dotenv-expand": "12.0.3", + "lodash": "4.18.1" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.28.tgz", + "integrity": "sha512-06m63xIRj8+l8uOeh/8LnYupGubkyu4f+bPKIadaSui6vK9KpXgoz7HveT1yOVLcEt0M0oCOEW5EuEXZkEmBBQ==", + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.4.2", + "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/jwt": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/platform-fastify": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-11.1.28.tgz", + "integrity": "sha512-utUfyxRzZsoFxz1GU3Z0OthHpijB605iqtxwFnk9yKowLrU1t1ZkxMUuad/csemImY6AsuaTpTjCecKbmfl+pQ==", + "license": "MIT", + "dependencies": { + "@fastify/cors": "11.2.0", + "@fastify/formbody": "8.0.2", + "fast-querystring": "1.1.2", + "fastify": "5.10.0", + "fastify-plugin": "6.0.0", + "find-my-way": "9.6.0", + "light-my-request": "6.6.0", + "path-to-regexp": "8.4.2", + "reusify": "1.1.0", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0 || ^9.0.0", + "@fastify/view": "^10.0.0 || ^11.0.0 || ^12.0.0", + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "@fastify/view": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.1.0.tgz", + "integrity": "sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.24", + "@angular-devkit/schematics": "19.2.24", + "comment-json": "5.0.0", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "typescript": ">=4.8.2" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.24.tgz", + "integrity": "sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "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.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.24.tgz", + "integrity": "sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.24", + "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/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/testing": { + "version": "11.1.28", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.28.tgz", + "integrity": "sha512-B+VgRxeLaH7jkOMgAyUP3N3rpFlisQ7JRxixRbgHvG6a0VgKbbkNSofKExexCgKmQQak80undb3+2kE1lUBmRQ==", + "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/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "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/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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", + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "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/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": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "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/@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/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "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/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "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": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "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/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-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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "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/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz", + "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.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==", + "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.11.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.10.tgz", + "integrity": "sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/better-sqlite3": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", + "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "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": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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==", + "dev": true, + "license": "MIT" + }, + "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/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "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/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "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/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" + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "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/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/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/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": "5.0.0", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-5.0.0.tgz", + "integrity": "sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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/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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", + "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "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/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/electron-to-chromium": { + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", + "dev": true, + "license": "ISC" + }, + "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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "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-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "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/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/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/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/esrecurse/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/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/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/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "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==", + "license": "MIT" + }, + "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-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz", + "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.10.0.tgz", + "integrity": "sha512-A9L0ziuWGQHgEEVgF3davQ9vbD93IuX+lo2IsxapQmu5b/Y/ynn9m9K5JHt9dvyJXOFc5iN0Zk5GHEOqnzhWjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-6.0.0.tgz", + "integrity": "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "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/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "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/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/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "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/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "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/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/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/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "license": "MIT", + "engines": { + "node": ">= 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-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-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-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/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/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/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/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.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "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==", + "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.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.10.tgz", + "integrity": "sha512-xJxrdqvbl2rtn2MaUJrUejz8J7/uZNC0V77oks2LxYrO/+ZtVpRmz+fEQMuu6VusnEB1fmpByiLS1WXecOnAnw==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "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.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "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.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.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/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/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-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/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "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.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "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/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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "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-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "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-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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/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/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/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "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/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.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "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/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "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" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "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", + "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/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": 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/schema-utils/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "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/schema-utils/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/schema-utils/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/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "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-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-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/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/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/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", + "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.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "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/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/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/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/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/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "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/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "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.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "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.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "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/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/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/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/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/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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..66135d9 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "child-gold-land-backend-nest", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/main.js", + "start:prod": "node dist/main.js", + "type-check": "tsc --noEmit -p tsconfig.json", + "test:integration": "tsx test/integration.test.ts", + "test:progression": "tsx test/progression.test.ts", + "test:cutover:g1a": "tsx ../scripts/cutover/run-g1a.ts", + "test:cutover:g1b": "tsx ../scripts/cutover/run-g1b.ts", + "test:cutover:g1b2b": "tsx ../scripts/cutover/run-g1b2b.ts", + "test:cutover:g1b:dry-run": "tsx ../scripts/cutover/g1b-dry-run.ts" + }, + "dependencies": { + "@nestjs/common": "^11.0.12", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.12", + "@nestjs/jwt": "^11.0.0", + "@nestjs/platform-fastify": "^11.0.12", + "bcryptjs": "^3.0.2", + "better-sqlite3": "^12.2.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.6", + "@nestjs/testing": "^11.0.12", + "@types/bcryptjs": "^2.4.6", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^24.13.3", + "tsx": "^4.20.3", + "typescript": "~5.8.3" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } +} diff --git a/src/achievements/achievements.controller.ts b/src/achievements/achievements.controller.ts new file mode 100644 index 0000000..d00111c --- /dev/null +++ b/src/achievements/achievements.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get, Inject } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { AchievementsService } from './achievements.service' + +@Controller('api/achievements') +export class AchievementsController { + constructor(@Inject(AchievementsService) private readonly achievements: AchievementsService) {} + + @Get() + getAchievements(@CurrentUser() userId: string) { + return this.achievements.getAchievements(userId) + } +} diff --git a/src/achievements/achievements.module.ts b/src/achievements/achievements.module.ts new file mode 100644 index 0000000..d9976b6 --- /dev/null +++ b/src/achievements/achievements.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common' +import { UsersModule } from '../users/users.module' +import { AchievementsController } from './achievements.controller' +import { AchievementsService } from './achievements.service' + +@Module({ + imports: [UsersModule], + controllers: [AchievementsController], + providers: [AchievementsService], +}) +export class AchievementsModule {} diff --git a/src/achievements/achievements.service.ts b/src/achievements/achievements.service.ts new file mode 100644 index 0000000..47f3e78 --- /dev/null +++ b/src/achievements/achievements.service.ts @@ -0,0 +1,33 @@ +import { Inject, Injectable } from '@nestjs/common' +import { ACHIEVEMENTS } from '../shared/catalog/achievements.constants' +import type { Achievement } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +export interface AchievementsResponse { + definitions: Array<{ + id: string + title: string + description: string + icon: string + }> + unlocked: Achievement[] +} + +@Injectable() +export class AchievementsService { + constructor(@Inject(UsersService) private readonly users: UsersService) {} + + getAchievements(userId: string): AchievementsResponse { + const state = this.users.getUserState(userId) + + return { + definitions: ACHIEVEMENTS.map(({ id, title, description, icon }) => ({ + id, + title, + description, + icon, + })), + unlocked: state.achievements, + } + } +} diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..7b958cc --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common' +import { ConfigModule } from '@nestjs/config' +import { validateEnv } from './config/env.validation' +import { DatabaseModule } from './database/database.module' +import { HealthModule } from './health/health.module' +import { GoldLandModule } from './gold-land/gold-land.module' +import { MarketModule } from './market/market.module' +import { UsersModule } from './users/users.module' +import { ChallengesModule } from './challenges/challenges.module' +import { GamesModule } from './games/games.module' +import { StoreModule } from './store/store.module' +import { WalletModule } from './wallet/wallet.module' +import { AuthModule } from './auth/auth.module' +import { AchievementsModule } from './achievements/achievements.module' + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + validate: validateEnv, + }), + DatabaseModule, + HealthModule, + GoldLandModule, + UsersModule, + WalletModule, + MarketModule, + StoreModule, + GamesModule, + ChallengesModule, + AchievementsModule, + AuthModule, + ], +}) +export class AppModule {} diff --git a/src/auth/auth-seed.service.ts b/src/auth/auth-seed.service.ts new file mode 100644 index 0000000..463a91e --- /dev/null +++ b/src/auth/auth-seed.service.ts @@ -0,0 +1,14 @@ +import { Inject, Injectable, Logger, OnModuleInit, forwardRef } from '@nestjs/common' +import { AuthService } from './auth.service' + +@Injectable() +export class AuthSeedService implements OnModuleInit { + private readonly logger = new Logger(AuthSeedService.name) + + constructor(@Inject(forwardRef(() => AuthService)) private readonly auth: AuthService) {} + + onModuleInit(): void { + this.auth.seedDemoAccount() + this.logger.log('Auth seed completed (demo account if missing)') + } +} diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..bb7273e --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, HttpCode, Inject, Post, forwardRef } from '@nestjs/common' +import { Public } from '../common/decorators/public.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { AuthService } from './auth.service' +import { LoginDto } from './dto/login.dto' +import { RegisterDto } from './dto/register.dto' + +@Controller('api/auth') +export class AuthController { + constructor(@Inject(forwardRef(() => AuthService)) private readonly auth: AuthService) {} + + @Public() + @HttpCode(200) + @Post('register') + register(@Body(strictBodyPipe(RegisterDto)) dto: RegisterDto) { + return this.auth.register(dto.username, dto.password, dto.name) + } + + @Public() + @HttpCode(200) + @Post('login') + login(@Body(strictBodyPipe(LoginDto)) dto: LoginDto) { + return this.auth.login(dto.username, dto.password) + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..ad11fcf --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,32 @@ +import { Module, forwardRef } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { APP_GUARD } from '@nestjs/core' +import { JwtModule } from '@nestjs/jwt' +import { UsersModule } from '../users/users.module' +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard' +import { AuthController } from './auth.controller' +import { AuthService } from './auth.service' +import { AuthSeedService } from './auth-seed.service' + +@Module({ + imports: [ + forwardRef(() => UsersModule), + JwtModule.registerAsync({ + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + secret: config.get('JWT_SECRET'), + }), + }), + ], + controllers: [AuthController], + providers: [ + AuthService, + AuthSeedService, + { + provide: APP_GUARD, + useClass: JwtAuthGuard, + }, + ], + exports: [AuthService, JwtModule], +}) +export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..1a6c2c2 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,205 @@ +import { randomUUID } from 'node:crypto' +import { + ConflictException, + Inject, + Injectable, + NotFoundException, + UnauthorizedException, + BadRequestException, + forwardRef, +} from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import bcrypt from 'bcryptjs' +import { DatabaseService } from '../database/database.service' +import { createDefaultUserState } from '../shared/catalog/default-user-state' +import type { AuthResponse } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +export interface AuthAccount { + id: string + username: string + passwordHash: string + userId: string + createdAt: string +} + +@Injectable() +export class AuthService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(forwardRef(() => UsersService)) + private readonly users: UsersService, + @Inject(JwtService) private readonly jwt: JwtService, + ) {} + + register(username: string, password: string, name?: string): AuthResponse { + const account = this.createAuthAccount(username, password, name ?? username) + return this.buildAuthResponse(account.userId, account.username) + } + + login(username: string, password: string): AuthResponse { + const account = this.verifyLogin(username, password) + return this.buildAuthResponse(account.userId, account.username) + } + + updatePassword(userId: string, currentPassword: string, newPassword: string): void { + const account = this.findAccountByUserId(userId) + if (!account) { + throw new NotFoundException('حساب کاربری پیدا نشد') + } + if (!bcrypt.compareSync(currentPassword, account.passwordHash)) { + throw new UnauthorizedException('رمز عبور فعلی اشتباه است') + } + if (newPassword.length < 6) { + throw new BadRequestException('رمز عبور جدید باید حداقل ۶ کاراکتر باشد') + } + + this.database + .getDb() + .prepare('UPDATE auth_accounts SET password_hash = ? WHERE user_id = ?') + .run(bcrypt.hashSync(newPassword, 10), userId) + } + + findAccountByUsername(username: string): AuthAccount | null { + const row = this.database + .getDb() + .prepare( + 'SELECT id, username, password_hash, user_id, created_at FROM auth_accounts WHERE username = ?', + ) + .get(username.trim().toLowerCase()) as + | { + id: string + username: string + password_hash: string + user_id: string + created_at: string + } + | undefined + + return row ? this.rowToAccount(row) : null + } + + findAccountByUserId(userId: string): AuthAccount | null { + const row = this.database + .getDb() + .prepare( + 'SELECT id, username, password_hash, user_id, created_at FROM auth_accounts WHERE user_id = ?', + ) + .get(userId) as + | { + id: string + username: string + password_hash: string + user_id: string + created_at: string + } + | undefined + + return row ? this.rowToAccount(row) : null + } + + seedDemoAccount(): void { + const demo = this.findAccountByUsername('demo') + if (demo) return + + this.users.ensureUser('demo') + const account: AuthAccount = { + id: randomUUID(), + username: 'demo', + passwordHash: bcrypt.hashSync('123456', 10), + userId: 'demo', + createdAt: new Date().toISOString(), + } + + this.database + .getDb() + .prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ) + .run(account.id, account.username, account.passwordHash, account.userId, account.createdAt) + } + + private createAuthAccount( + username: string, + password: string, + displayName: string, + ): AuthAccount { + const normalized = username.trim().toLowerCase() + if (normalized.length < 3) { + throw new BadRequestException('نام کاربری باید حداقل ۳ کاراکتر باشد') + } + if (password.length < 6) { + throw new BadRequestException('رمز عبور باید حداقل ۶ کاراکتر باشد') + } + if (this.findAccountByUsername(normalized)) { + throw new ConflictException('این نام کاربری قبلاً ثبت شده') + } + + const userId = randomUUID() + const state = createDefaultUserState(userId) + state.name = displayName.trim() || username.trim() + const now = new Date().toISOString() + + const account: AuthAccount = { + id: randomUUID(), + username: normalized, + passwordHash: bcrypt.hashSync(password, 10), + userId, + createdAt: now, + } + + this.database.transaction(() => { + this.database + .getDb() + .prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)') + .run(userId, JSON.stringify(state), now) + + this.database + .getDb() + .prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ) + .run(account.id, account.username, account.passwordHash, account.userId, account.createdAt) + }) + + return account + } + + private verifyLogin(username: string, password: string): AuthAccount { + const account = this.findAccountByUsername(username) + if (!account || !bcrypt.compareSync(password, account.passwordHash)) { + throw new UnauthorizedException('نام کاربری یا رمز عبور اشتباه است') + } + this.users.ensureUser(account.userId) + return account + } + + private buildAuthResponse(userId: string, username: string): AuthResponse { + const state = this.users.getUserState(userId) + return { + token: this.jwt.sign({ userId }), + user: { + id: state.id, + name: state.name, + username, + }, + me: this.users.toMeResponse(state), + } + } + + private rowToAccount(row: { + id: string + username: string + password_hash: string + user_id: string + created_at: string + }): AuthAccount { + return { + id: row.id, + username: row.username, + passwordHash: row.password_hash, + userId: row.user_id, + createdAt: row.created_at, + } + } +} diff --git a/src/auth/dto/login.dto.ts b/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..ac34ac3 --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,9 @@ +import { IsString } from 'class-validator' + +export class LoginDto { + @IsString() + username!: string + + @IsString() + password!: string +} diff --git a/src/auth/dto/register.dto.ts b/src/auth/dto/register.dto.ts new file mode 100644 index 0000000..c3a83b4 --- /dev/null +++ b/src/auth/dto/register.dto.ts @@ -0,0 +1,13 @@ +import { IsOptional, IsString } from 'class-validator' + +export class RegisterDto { + @IsString() + username!: string + + @IsString() + password!: string + + @IsOptional() + @IsString() + name?: string +} diff --git a/src/challenges/challenges.controller.ts b/src/challenges/challenges.controller.ts new file mode 100644 index 0000000..9991449 --- /dev/null +++ b/src/challenges/challenges.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Get, HttpCode, Inject, Post } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { ChallengesService } from './challenges.service' +import { CompleteDailyChallengeDto } from './dto/complete-daily-challenge.dto' + +@Controller('api/challenges') +export class ChallengesController { + constructor(@Inject(ChallengesService) private readonly challenges: ChallengesService) {} + + @Get('daily') + getDailyChallenge(@CurrentUser() userId: string) { + return this.challenges.getDailyChallenge(userId) + } + + @HttpCode(200) + @Post('daily/complete') + completeDailyChallenge( + @CurrentUser() userId: string, + @Body(strictBodyPipe(CompleteDailyChallengeDto)) dto: CompleteDailyChallengeDto, + ) { + return this.challenges.completeDailyChallenge(userId, dto.optionId) + } +} diff --git a/src/challenges/challenges.module.ts b/src/challenges/challenges.module.ts new file mode 100644 index 0000000..c2a0192 --- /dev/null +++ b/src/challenges/challenges.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { ChallengesController } from './challenges.controller' +import { ChallengesService } from './challenges.service' + +@Module({ + imports: [DatabaseModule, UsersModule], + controllers: [ChallengesController], + providers: [ChallengesService], +}) +export class ChallengesModule {} diff --git a/src/challenges/challenges.service.ts b/src/challenges/challenges.service.ts new file mode 100644 index 0000000..b90db34 --- /dev/null +++ b/src/challenges/challenges.service.ts @@ -0,0 +1,68 @@ +import { BadRequestException, Inject, Injectable } from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { + CHALLENGE_OPTIONS, + DAILY_CHALLENGE, + type DailyChallengeCompleteResponse, + type DailyChallengeResponse, +} from '../shared/catalog/challenge.constants' +import { addTransaction, addXp, incrementMission } from '../shared/economy/user-economy.helpers' +import { UsersService } from '../users/users.service' + +@Injectable() +export class ChallengesService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(UsersService) private readonly users: UsersService, + ) {} + + getDailyChallenge(userId: string): DailyChallengeResponse { + const state = this.users.getUserState(userId) + return { + ...DAILY_CHALLENGE, + options: CHALLENGE_OPTIONS.map(({ id, label, icon, color }) => ({ + id, + label, + icon, + color, + })), + completed: state.completedChallenges.includes(DAILY_CHALLENGE.id), + } + } + + completeDailyChallenge(userId: string, optionId: string): DailyChallengeCompleteResponse { + const option = CHALLENGE_OPTIONS.find((o) => o.id === optionId) + if (!option) { + throw new BadRequestException('گزینه نامعتبر است') + } + + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + + if (state.completedChallenges.includes(DAILY_CHALLENGE.id)) { + return { + ...this.users.toMeResponse(state), + feedback: option.feedback, + alreadyCompleted: true, + } + } + + state.completedChallenges.push(DAILY_CHALLENGE.id) + state.coins += DAILY_CHALLENGE.coinReward + addXp(state, DAILY_CHALLENGE.xpReward) + incrementMission(state) + addTransaction(state, { + type: 'income', + title: 'چالش روزانه', + amount: DAILY_CHALLENGE.coinReward, + unit: 'سکه', + }) + this.users.saveUserState(state) + return { + ...this.users.toMeResponse(state), + feedback: option.feedback, + alreadyCompleted: false, + } + }) + } +} diff --git a/src/challenges/dto/complete-daily-challenge.dto.ts b/src/challenges/dto/complete-daily-challenge.dto.ts new file mode 100644 index 0000000..9948f83 --- /dev/null +++ b/src/challenges/dto/complete-daily-challenge.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from 'class-validator' + +export class CompleteDailyChallengeDto { + @IsString() + @IsNotEmpty() + optionId!: string +} diff --git a/src/common/decorators/current-user.decorator.ts b/src/common/decorators/current-user.decorator.ts new file mode 100644 index 0000000..bdd42fb --- /dev/null +++ b/src/common/decorators/current-user.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common' +import type { AuthenticatedRequest } from '../types/authenticated-request' + +export const CurrentUser = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string => { + const request = ctx.switchToHttp().getRequest() + return request.userId + }, +) diff --git a/src/common/decorators/public.decorator.ts b/src/common/decorators/public.decorator.ts new file mode 100644 index 0000000..91c2399 --- /dev/null +++ b/src/common/decorators/public.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common' + +export const IS_PUBLIC_KEY = 'isPublic' +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true) diff --git a/src/common/filters/api-exception.filter.ts b/src/common/filters/api-exception.filter.ts new file mode 100644 index 0000000..a90e373 --- /dev/null +++ b/src/common/filters/api-exception.filter.ts @@ -0,0 +1,61 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, + Logger, +} from '@nestjs/common' +import type { FastifyReply } from 'fastify' + +interface ApiErrorBody { + error: string +} + +@Catch() +export class ApiExceptionFilter implements ExceptionFilter { + private readonly logger = new Logger(ApiExceptionFilter.name) + + catch(exception: unknown, host: ArgumentsHost): void { + const ctx = host.switchToHttp() + const reply = ctx.getResponse() + + if (exception instanceof HttpException) { + const status = exception.getStatus() + const message = this.extractMessage(exception) + reply.status(status).send({ error: message } satisfies ApiErrorBody) + return + } + + this.logger.error(exception) + reply.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ + error: 'خطای سرور', + } satisfies ApiErrorBody) + } + + private extractMessage(exception: HttpException): string { + const response = exception.getResponse() + + if (typeof response === 'string') { + return response + } + + if (typeof response === 'object' && response !== null) { + const body = response as Record + + if (typeof body.message === 'string') { + return body.message + } + + if (Array.isArray(body.message) && body.message.length > 0) { + return body.message.map(String).join('؛ ') + } + + if (typeof body.error === 'string') { + return body.error + } + } + + return 'درخواست نامعتبر است' + } +} diff --git a/src/common/guards/jwt-auth.guard.ts b/src/common/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..5954fed --- /dev/null +++ b/src/common/guards/jwt-auth.guard.ts @@ -0,0 +1,55 @@ +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, + forwardRef, +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { JwtService } from '@nestjs/jwt' +import { IS_PUBLIC_KEY } from '../decorators/public.decorator' +import type { AuthenticatedRequest } from '../types/authenticated-request' +import { UsersService } from '../../users/users.service' + +interface JwtPayload { + userId: string +} + +@Injectable() +export class JwtAuthGuard implements CanActivate { + constructor( + @Inject(JwtService) private readonly jwt: JwtService, + @Inject(Reflector) private readonly reflector: Reflector, + @Inject(forwardRef(() => UsersService)) + private readonly users: UsersService, + ) {} + + canActivate(context: ExecutionContext): boolean { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]) + if (isPublic) return true + + const request = context.switchToHttp().getRequest() + const authHeader = request.headers.authorization + + if (!authHeader?.startsWith('Bearer ')) { + throw new UnauthorizedException('لطفاً وارد شوید') + } + + try { + const token = authHeader.slice('Bearer '.length) + const payload = this.jwt.verify(token) + if (!payload?.userId || typeof payload.userId !== 'string') { + throw new Error('invalid payload') + } + this.users.ensureUser(payload.userId) + request.userId = payload.userId + return true + } catch { + throw new UnauthorizedException('نشست نامعتبر است. دوباره وارد شوید') + } + } +} diff --git a/src/common/pipes/strict-body.pipe.ts b/src/common/pipes/strict-body.pipe.ts new file mode 100644 index 0000000..55aa07e --- /dev/null +++ b/src/common/pipes/strict-body.pipe.ts @@ -0,0 +1,10 @@ +import { ValidationPipe, type Type } from '@nestjs/common' + +export function strictBodyPipe(dto: Type) { + return new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + expectedType: dto, + }) +} diff --git a/src/common/types/authenticated-request.ts b/src/common/types/authenticated-request.ts new file mode 100644 index 0000000..f1c8540 --- /dev/null +++ b/src/common/types/authenticated-request.ts @@ -0,0 +1,5 @@ +import type { FastifyRequest } from 'fastify' + +export interface AuthenticatedRequest extends FastifyRequest { + userId: string +} diff --git a/src/config/config.module.ts b/src/config/config.module.ts new file mode 100644 index 0000000..68f1a15 --- /dev/null +++ b/src/config/config.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common' +import { ConfigModule as NestConfigModule } from '@nestjs/config' +import { validateEnv } from './env.validation' + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + validate: validateEnv, + }), + ], +}) +export class AppConfigModule {} diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts new file mode 100644 index 0000000..fbc047c --- /dev/null +++ b/src/config/env.validation.ts @@ -0,0 +1,60 @@ +import { plainToInstance } from 'class-transformer' +import { IsIn, IsInt, IsOptional, IsString, Min, validateSync } from 'class-validator' + +export class EnvironmentVariables { + @IsIn(['development', 'production', 'test']) + NODE_ENV!: 'development' | 'production' | 'test' + + @IsInt() + @Min(1) + PORT!: number + + @IsString() + HOST!: string + + @IsString() + CORS_ORIGIN!: string + + @IsOptional() + @IsString() + DB_PATH?: string + + @IsOptional() + @IsString() + JWT_SECRET?: string +} + +export function validateEnv(config: Record): EnvironmentVariables { + const nodeEnv = (config.NODE_ENV as string | undefined) ?? 'development' + const jwtSecret = config.JWT_SECRET as string | undefined + + if (nodeEnv === 'production' && (!jwtSecret || jwtSecret.trim().length === 0)) { + throw new Error('JWT_SECRET is required when NODE_ENV=production') + } + + if (nodeEnv !== 'production' && !jwtSecret) { + console.warn( + '[config] JWT_SECRET is not set — using development fallback. Do not use in production.', + ) + } + + const normalized = { + NODE_ENV: nodeEnv, + PORT: Number(config.PORT ?? 3001), + HOST: (config.HOST as string | undefined) ?? '0.0.0.0', + CORS_ORIGIN: (config.CORS_ORIGIN as string | undefined) ?? 'http://localhost:5173', + DB_PATH: config.DB_PATH as string | undefined, + JWT_SECRET: jwtSecret ?? 'gold-land-dev-secret-change-in-production', + } + + const validated = plainToInstance(EnvironmentVariables, normalized, { + enableImplicitConversion: true, + }) + + const errors = validateSync(validated, { skipMissingProperties: false }) + if (errors.length > 0) { + throw new Error(errors.toString()) + } + + return validated +} diff --git a/src/database/database.module.ts b/src/database/database.module.ts new file mode 100644 index 0000000..ac8649a --- /dev/null +++ b/src/database/database.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common' +import { DatabaseService } from './database.service' + +@Global() +@Module({ + providers: [DatabaseService], + exports: [DatabaseService], +}) +export class DatabaseModule {} diff --git a/src/database/database.service.ts b/src/database/database.service.ts new file mode 100644 index 0000000..b798fe4 --- /dev/null +++ b/src/database/database.service.ts @@ -0,0 +1,55 @@ +import { Inject, Injectable, Logger, OnModuleDestroy, OnModuleInit, Optional } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import Database from 'better-sqlite3' +import { mkdirSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { EXPECTED_TABLES, listUserTables, runSchemaMigrations } from './migration.service' + +@Injectable() +export class DatabaseService implements OnModuleInit, OnModuleDestroy { + private readonly logger = new Logger(DatabaseService.name) + private db: Database.Database | null = null + private readonly dbPath: string + + constructor(@Optional() @Inject(ConfigService) private readonly config?: ConfigService) { + const configured = this.config?.get('DB_PATH') ?? process.env.DB_PATH + this.dbPath = configured ?? join(process.cwd(), 'data', 'app.db') + } + + onModuleInit(): void { + mkdirSync(dirname(this.dbPath), { recursive: true }) + this.db = new Database(this.dbPath) + this.db.pragma('journal_mode = WAL') + runSchemaMigrations(this.db) + this.logger.log(`SQLite ready at ${this.dbPath}`) + } + + onModuleDestroy(): void { + if (this.db) { + this.db.close() + this.db = null + this.logger.log('SQLite connection closed') + } + } + + getDbPath(): string { + return this.dbPath + } + + getDb(): Database.Database { + if (!this.db) { + throw new Error('Database is not initialized') + } + return this.db + } + + transaction(fn: () => T): T { + return this.getDb().transaction(fn)() + } + + verifyMigrationsApplied(): { ok: boolean; tables: string[]; missing: string[] } { + const tables = listUserTables(this.getDb()) + const missing = EXPECTED_TABLES.filter((name) => !tables.includes(name)) + return { ok: missing.length === 0, tables, missing } + } +} diff --git a/src/database/database.tokens.ts b/src/database/database.tokens.ts new file mode 100644 index 0000000..59de235 --- /dev/null +++ b/src/database/database.tokens.ts @@ -0,0 +1 @@ +export const DATABASE_PATH = Symbol('DATABASE_PATH') diff --git a/src/database/migration.service.ts b/src/database/migration.service.ts new file mode 100644 index 0000000..2b1b23d --- /dev/null +++ b/src/database/migration.service.ts @@ -0,0 +1,65 @@ +import type Database from 'better-sqlite3' + +/** Idempotent schema setup — mirrors backend/db.ts + backend/migrations.ts (no destructive ops). */ +export function runSchemaMigrations(db: Database.Database): void { + db.exec(` + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + state_json TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS user_level_progress ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + level_id INTEGER NOT NULL, + best_score INTEGER NOT NULL DEFAULT 0, + stars INTEGER NOT NULL DEFAULT 0, + attempts_count INTEGER NOT NULL DEFAULT 0, + started_at TEXT, + completed_at TEXT, + updated_at TEXT NOT NULL, + UNIQUE(user_id, level_id) + ); + + CREATE TABLE IF NOT EXISTS reward_ledger ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + source_type TEXT NOT NULL, + source_id TEXT NOT NULL, + reward_key TEXT NOT NULL, + xp_delta INTEGER NOT NULL DEFAULT 0, + coins_delta INTEGER NOT NULL DEFAULT 0, + metadata_json TEXT, + created_at TEXT NOT NULL, + UNIQUE(user_id, reward_key) + ); + + CREATE INDEX IF NOT EXISTS idx_user_level_progress_user + ON user_level_progress(user_id); + CREATE INDEX IF NOT EXISTS idx_reward_ledger_user + ON reward_ledger(user_id); + + CREATE TABLE IF NOT EXISTS auth_accounts ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + user_id TEXT NOT NULL, + created_at TEXT NOT NULL + ); + `) +} + +export const EXPECTED_TABLES = [ + 'users', + 'user_level_progress', + 'reward_ledger', + 'auth_accounts', +] as const + +export function listUserTables(db: Database.Database): string[] { + const rows = db + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'") + .all() as Array<{ name: string }> + return rows.map((row) => row.name) +} diff --git a/src/games/dto/complete-game.dto.ts b/src/games/dto/complete-game.dto.ts new file mode 100644 index 0000000..13f2277 --- /dev/null +++ b/src/games/dto/complete-game.dto.ts @@ -0,0 +1 @@ +export class CompleteGameDto {} diff --git a/src/games/games.controller.ts b/src/games/games.controller.ts new file mode 100644 index 0000000..7c29d64 --- /dev/null +++ b/src/games/games.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, Get, HttpCode, Inject, Param, Post } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { CompleteGameDto } from './dto/complete-game.dto' +import { GamesService } from './games.service' + +@Controller('api/games') +export class GamesController { + constructor(@Inject(GamesService) private readonly games: GamesService) {} + + @Get() + getGames() { + return this.games.getGames() + } + + @HttpCode(200) + @Post(':gameId/complete') + completeGame( + @CurrentUser() userId: string, + @Param('gameId') gameId: string, + @Body(strictBodyPipe(CompleteGameDto)) _dto: CompleteGameDto, + ) { + return this.games.completeGame(userId, gameId) + } +} diff --git a/src/games/games.module.ts b/src/games/games.module.ts new file mode 100644 index 0000000..0372b47 --- /dev/null +++ b/src/games/games.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { GamesController } from './games.controller' +import { GamesService } from './games.service' + +@Module({ + imports: [DatabaseModule, UsersModule], + controllers: [GamesController], + providers: [GamesService], +}) +export class GamesModule {} diff --git a/src/games/games.service.ts b/src/games/games.service.ts new file mode 100644 index 0000000..dc70cd2 --- /dev/null +++ b/src/games/games.service.ts @@ -0,0 +1,57 @@ +import { Inject, Injectable, NotFoundException } from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { GAMES, type GameDefinition } from '../shared/catalog/games.constants' +import { addTransaction, addXp } from '../shared/economy/user-economy.helpers' +import type { MeResponse } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +@Injectable() +export class GamesService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(UsersService) private readonly users: UsersService, + ) {} + + getGames(): GameDefinition[] { + return GAMES + } + + completeGame(userId: string, gameId: string): MeResponse { + const game = GAMES.find((g) => g.id === gameId) + if (!game) { + throw new NotFoundException('بازی پیدا نشد') + } + + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + + if (state.claimedRewards.includes(gameId)) { + return this.users.toMeResponse(state) + } + + state.claimedRewards.push(gameId) + if (!state.completedGames.includes(gameId)) { + state.completedGames.push(gameId) + } + + const rewardCoins = 'rewardCoins' in game ? game.rewardCoins ?? 0 : 0 + const rewardXp = 'rewardXp' in game ? game.rewardXp ?? 0 : 0 + + if (rewardCoins > 0) { + state.coins += rewardCoins + addTransaction(state, { + type: 'income', + title: 'پاداش بازی', + amount: rewardCoins, + unit: 'سکه', + }) + } + if (rewardXp > 0) { + addXp(state, rewardXp) + } + + this.users.saveUserState(state) + return this.users.toMeResponse(state) + }) + } +} diff --git a/src/gold-land/dto/complete-level.dto.ts b/src/gold-land/dto/complete-level.dto.ts new file mode 100644 index 0000000..efc0c74 --- /dev/null +++ b/src/gold-land/dto/complete-level.dto.ts @@ -0,0 +1,35 @@ +import { Type } from 'class-transformer' +import { + IsArray, + IsNumber, + IsOptional, + IsString, + Min, + ValidateNested, +} from 'class-validator' + +export class LessonAnswerDto { + @IsString() + questionId!: string + + @IsOptional() + @IsString() + answerId?: string + + @IsOptional() + @IsArray() + @IsString({ each: true }) + orderedItemIds?: string[] +} + +export class CompleteLevelDto { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => LessonAnswerDto) + answers!: LessonAnswerDto[] + + @IsOptional() + @IsNumber() + @Min(0) + durationMs?: number +} diff --git a/src/gold-land/exceptions/progression.exception.ts b/src/gold-land/exceptions/progression.exception.ts new file mode 100644 index 0000000..a9c27e0 --- /dev/null +++ b/src/gold-land/exceptions/progression.exception.ts @@ -0,0 +1,10 @@ +import { HttpException } from '@nestjs/common' + +export class ProgressionError extends HttpException { + readonly statusCode: number + + constructor(statusCode: number, message: string) { + super(message, statusCode) + this.statusCode = statusCode + } +} diff --git a/src/gold-land/gold-land.controller.ts b/src/gold-land/gold-land.controller.ts new file mode 100644 index 0000000..5bf8915 --- /dev/null +++ b/src/gold-land/gold-land.controller.ts @@ -0,0 +1,87 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + ParseIntPipe, + Post, + Body, + forwardRef, +} from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { GOLD_LAND_LEVELS } from './levels/levels' +import { CompleteLevelDto } from './dto/complete-level.dto' +import type { LessonSubmissionAnswer } from './lessons/lesson.types' +import { GoldLandProgressionService } from './services/progression.service' + +@Controller('api/gold-land') +export class GoldLandController { + constructor( + @Inject(forwardRef(() => GoldLandProgressionService)) + private readonly progression: GoldLandProgressionService, + ) {} + + @Get('levels') + getLevels() { + return { + levels: GOLD_LAND_LEVELS.map( + ({ + id, + order, + slug, + title, + description, + activityType, + activityKey, + activityRoute, + rewardXp, + rewardCoins, + isActive, + }) => ({ + id, + order, + slug, + title, + description, + activityType, + activityKey, + activityRoute, + rewardXp, + rewardCoins, + isActive, + }), + ), + } + } + + @Get('progress') + getProgress(@CurrentUser() userId: string) { + return this.progression.getGoldLandProgress(userId) + } + + @Get('levels/:levelId') + getLevel(@CurrentUser() userId: string, @Param('levelId', ParseIntPipe) levelId: number) { + return this.progression.getGoldLandLevel(userId, levelId) + } + + @HttpCode(200) + @Post('levels/:levelId/start') + startLevel(@CurrentUser() userId: string, @Param('levelId', ParseIntPipe) levelId: number) { + return this.progression.startLevel(userId, levelId) + } + + @HttpCode(200) + @Post('levels/:levelId/complete') + completeLevel( + @CurrentUser() userId: string, + @Param('levelId', ParseIntPipe) levelId: number, + @Body(strictBodyPipe(CompleteLevelDto)) dto: CompleteLevelDto, + ) { + return this.progression.completeLevel(userId, levelId, { + answers: dto.answers as LessonSubmissionAnswer[], + durationMs: dto.durationMs, + }) + } +} diff --git a/src/gold-land/gold-land.module.ts b/src/gold-land/gold-land.module.ts new file mode 100644 index 0000000..808c056 --- /dev/null +++ b/src/gold-land/gold-land.module.ts @@ -0,0 +1,14 @@ +import { Module, forwardRef } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { GoldLandController } from './gold-land.controller' +import { GoldLandProgressionService } from './services/progression.service' +import { RewardService } from './services/reward.service' + +@Module({ + imports: [DatabaseModule, forwardRef(() => UsersModule)], + controllers: [GoldLandController], + providers: [GoldLandProgressionService, RewardService], + exports: [GoldLandProgressionService], +}) +export class GoldLandModule {} diff --git a/src/gold-land/lessons/gold-market.lesson.ts b/src/gold-land/lessons/gold-market.lesson.ts new file mode 100644 index 0000000..e9ef2b7 --- /dev/null +++ b/src/gold-land/lessons/gold-market.lesson.ts @@ -0,0 +1,73 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const goldMarketLesson: LessonDefinition = { + id: 'gold-market', + version: 1, + title: 'بازار طلا', + steps: [ + { + id: 'market-story', + type: 'story', + title: 'نوسان بازار', + body: 'امروز قیمت طلا بالا رفت. دیروز پایین بود. علی می‌خواست بدون فکر همهٔ پس‌اندازش را بخرند. دوستش گفت: «اول ببین و مقایسه کن!»', + imageKey: 'gold-chart', + }, + { + id: 'market-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: 'در بازار طلا چه اتفاقی می‌افتد؟', + options: [ + { id: 'option-a', label: 'قیمت بالا و پایین می‌شود' }, + { id: 'option-b', label: 'قیمت همیشه ثابت است' }, + { id: 'option-c', label: 'فقط یک بار در سال تغییر می‌کند' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'market-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'اگر دیروز قیمت بالا رفته، لزوماً فردا هم بالا می‌رود.', + correctAnswerId: 'false', + }, + { + id: 'market-q3', + type: 'single_choice', + title: 'سؤال ۳', + question: 'چرا تصمیم عجولانه می‌تواند اشتباه باشد؟', + options: [ + { id: 'option-a', label: 'چون ممکن است وقت کافی برای فکر کردن نداشته باشیم' }, + { id: 'option-b', label: 'چون بازار همیشه بسته است' }, + { id: 'option-c', label: 'چون طلا رنگ ندارد' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'market-q4', + type: 'true_false', + title: 'سؤال ۴', + question: 'قبل از خرید، مقایسه و مشاهدهٔ قیمت‌ها مفید است.', + correctAnswerId: 'true', + }, + { + id: 'market-q5', + type: 'single_choice', + title: 'سؤال ۵', + question: 'بهترین رویکرد برای سرمایه‌گذاری چیست؟', + options: [ + { id: 'option-a', label: 'همهٔ پول را بدون فکر در یک جا گذاشتن' }, + { id: 'option-b', label: 'فکر کردن و پراکنده نکردن تمام پول در یک دارایی' }, + { id: 'option-c', label: 'هرگز هیچ چیز نخریدن' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'market-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'بازار طلا تغییر می‌کند. با آرامش نگاه کن، مقایسه کن و عجله نکن.', + highlight: 'رشد گذشته، آینده را تضمین نمی‌کند.', + }, + ], +} diff --git a/src/gold-land/lessons/inflation.lesson.ts b/src/gold-land/lessons/inflation.lesson.ts new file mode 100644 index 0000000..e72e3b7 --- /dev/null +++ b/src/gold-land/lessons/inflation.lesson.ts @@ -0,0 +1,73 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const inflationLesson: LessonDefinition = { + id: 'inflation', + version: 2, + title: 'تورم', + steps: [ + { + id: 'inflation-story', + type: 'story', + title: 'قیمت‌ها بالا می‌روند', + body: 'تورم یعنی قیمت چیزها با گذشت زمان بیشتر می‌شود و پول ما ارزشش را از دست می‌دهد.', + imageKey: 'shopping-cart', + }, + { + id: 'inflation-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: 'تورم یعنی چه؟', + options: [ + { id: 'option-a', label: 'قیمت‌ها با گذشت زمان کمتر می‌شوند' }, + { id: 'option-b', label: 'افزایش عمومی قیمت‌ها در طول زمان' }, + { id: 'option-c', label: 'پول همیشه ارزش ثابت دارد' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'inflation-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'در تورم، با همان مقدار پول می‌توان کالاهای کمتری خرید.', + correctAnswerId: 'true', + }, + { + id: 'inflation-q3', + type: 'single_choice', + title: 'سؤال ۳', + question: 'اگر دیروز ۱۰۰٬۰۰۰ تومان کافی بود و امروز ۱۱۲٬۰۰۰ تومان لازم است، چه اتفاقی افتاده؟', + options: [ + { id: 'option-a', label: 'قدرت خرید پول کم شده' }, + { id: 'option-b', label: 'قیمت‌ها کاهش یافته' }, + { id: 'option-c', label: 'تورم رخ نداده' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'inflation-q4', + type: 'true_false', + title: 'سؤال ۴', + question: 'گران شدن فقط یک کالا همیشه به معنی تورم است.', + correctAnswerId: 'false', + }, + { + id: 'inflation-q5', + type: 'single_choice', + title: 'سؤال ۵', + question: 'پس‌انداز بدون برنامه در برابر تورم چه می‌شود؟', + options: [ + { id: 'option-a', label: 'همیشه ارزشش بیشتر می‌شود' }, + { id: 'option-b', label: 'ممکن است ارزش واقعی‌اش کم شود' }, + { id: 'option-c', label: 'تغییری نمی‌کند' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'inflation-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'تورم باعث می‌شود پول تو کم‌کم قدرت خریدش را از دست بدهد.', + highlight: 'همان مقدار پول، در آینده ممکن است کالای کمتری بخرد.', + }, + ], +} diff --git a/src/gold-land/lessons/lesson.grader.ts b/src/gold-land/lessons/lesson.grader.ts new file mode 100644 index 0000000..e3c1a2b --- /dev/null +++ b/src/gold-land/lessons/lesson.grader.ts @@ -0,0 +1,146 @@ +import type { + GradeLessonResult, + GradableQuestion, + LessonDefinition, + LessonStep, + LessonSubmissionAnswer, + SortStep, + SingleChoiceStep, + TrueFalseStep, +} from './lesson.types.js' +import { isChoiceLessonAnswer, isSortLessonAnswer } from './lesson.types.js' + +export class LessonGradingError extends Error { + statusCode: number + + constructor(statusCode: number, message: string) { + super(message) + this.statusCode = statusCode + } +} + +function isGradableStep(step: LessonStep): step is SingleChoiceStep | TrueFalseStep | SortStep { + return step.type === 'single_choice' || step.type === 'true_false' || step.type === 'sort' +} + +export function getGradableQuestions(lesson: LessonDefinition): GradableQuestion[] { + return lesson.steps + .filter(isGradableStep) + .map((step) => ({ questionId: step.id, step })) +} + +function validateChoiceAnswer( + step: SingleChoiceStep | TrueFalseStep, + answerId: string, +): void { + if (step.type === 'single_choice') { + if (!step.options.some((o) => o.id === answerId)) { + throw new LessonGradingError(400, `گزینهٔ نامعتبر برای سؤال ${step.id}`) + } + return + } + if (answerId !== 'true' && answerId !== 'false') { + throw new LessonGradingError(400, `پاسخ true/false نامعتبر برای سؤال ${step.id}`) + } +} + +function validateSortAnswer(step: SortStep, orderedItemIds: string[]): void { + if (!Array.isArray(orderedItemIds)) { + throw new LessonGradingError(400, `ترتیب نامعتبر برای سؤال ${step.id}`) + } + const validIds = new Set(step.items.map((i) => i.id)) + if (orderedItemIds.length !== step.items.length) { + throw new LessonGradingError(400, `تعداد آیتم‌های sort برای سؤال ${step.id} نادرست است`) + } + const seen = new Set() + for (const id of orderedItemIds) { + if (typeof id !== 'string' || !validIds.has(id)) { + throw new LessonGradingError(400, `آیتم sort نامعتبر برای سؤال ${step.id}`) + } + if (seen.has(id)) { + throw new LessonGradingError(400, `آیتم تکراری در sort سؤال ${step.id}`) + } + seen.add(id) + } +} + +function isAnswerCorrect( + step: SingleChoiceStep | TrueFalseStep | SortStep, + answer: LessonSubmissionAnswer, +): boolean { + if (step.type === 'sort') { + if (!isSortLessonAnswer(answer)) return false + return answer.orderedItemIds.join(',') === step.correctOrder.join(',') + } + if (!isChoiceLessonAnswer(answer)) return false + return answer.answerId === step.correctAnswerId +} + +export function validateDurationMs(durationMs: unknown): void { + if (durationMs === undefined || durationMs === null) return + if (typeof durationMs !== 'number' || !Number.isFinite(durationMs) || durationMs < 0) { + throw new LessonGradingError(400, 'durationMs نامعتبر است') + } +} + +export function gradeLessonSubmission( + lesson: LessonDefinition, + answers: LessonSubmissionAnswer[], +): GradeLessonResult { + const gradable = getGradableQuestions(lesson) + if (gradable.length === 0) { + throw new LessonGradingError(400, 'این درس سؤال قابل ارزیابی ندارد') + } + + if (!Array.isArray(answers)) { + throw new LessonGradingError(400, 'فرمت پاسخ‌ها نامعتبر است') + } + + const byQuestion = new Map() + for (const answer of answers) { + if (!answer?.questionId || typeof answer.questionId !== 'string') { + throw new LessonGradingError(400, 'questionId نامعتبر است') + } + if (byQuestion.has(answer.questionId)) { + throw new LessonGradingError(400, `سؤال ${answer.questionId} تکراری است`) + } + byQuestion.set(answer.questionId, answer) + } + + let correctCount = 0 + + for (const { questionId, step } of gradable) { + const submitted = byQuestion.get(questionId) + if (!submitted) { + throw new LessonGradingError(400, `پاسخ سؤال ${questionId} ارسال نشده`) + } + + if (step.type === 'sort') { + if (!isSortLessonAnswer(submitted)) { + throw new LessonGradingError(400, `فرمت sort برای سؤال ${questionId} نامعتبر است`) + } + validateSortAnswer(step, submitted.orderedItemIds) + } else { + if (!isChoiceLessonAnswer(submitted)) { + throw new LessonGradingError(400, `فرمت پاسخ برای سؤال ${questionId} نامعتبر است`) + } + validateChoiceAnswer(step, submitted.answerId) + } + + if (isAnswerCorrect(step, submitted)) { + correctCount += 1 + } + } + + for (const answer of answers) { + const known = gradable.some((g) => g.questionId === answer.questionId) + if (!known) { + throw new LessonGradingError(400, `سؤال ${answer.questionId} پیدا نشد`) + } + } + + const totalQuestions = gradable.length + const score = Math.round((correctCount / totalQuestions) * 100) + + return { score, correctCount, totalQuestions } +} diff --git a/src/gold-land/lessons/lesson.loader.ts b/src/gold-land/lessons/lesson.loader.ts new file mode 100644 index 0000000..98d2471 --- /dev/null +++ b/src/gold-land/lessons/lesson.loader.ts @@ -0,0 +1,6 @@ +import { getPlayableLesson } from './lesson.registry.js' +import type { LessonDefinition } from './lesson.types.js' + +export function loadLessonForLevel(activityKey: string): LessonDefinition | null { + return getPlayableLesson(activityKey) +} diff --git a/src/gold-land/lessons/lesson.registry.ts b/src/gold-land/lessons/lesson.registry.ts new file mode 100644 index 0000000..5806ae9 --- /dev/null +++ b/src/gold-land/lessons/lesson.registry.ts @@ -0,0 +1,39 @@ +import type { LessonDefinition } from './lesson.types.js' +import { inflationLesson } from './inflation.lesson.js' +import { whatIsMoneyLesson } from './what-is-money.lesson.js' +import { savingLesson } from './saving.lesson.js' +import { needsAndWantsLesson } from './needs-and-wants.lesson.js' +import { whatIsGoldLesson } from './what-is-gold.lesson.js' +import { goldMarketLesson } from './gold-market.lesson.js' +import { getGradableQuestions } from './lesson.grader.js' + +const PLAYABLE_LESSONS: Record = { + 'what-is-money': whatIsMoneyLesson, + saving: savingLesson, + 'needs-and-wants': needsAndWantsLesson, + 'what-is-gold': whatIsGoldLesson, + 'gold-market': goldMarketLesson, + inflation: inflationLesson, +} + +export function getPlayableLesson(activityKey: string): LessonDefinition | null { + return PLAYABLE_LESSONS[activityKey] ?? null +} + +export function hasPlayableLesson(activityKey: string): boolean { + return activityKey in PLAYABLE_LESSONS +} + +export function getAllPlayableLessons(): LessonDefinition[] { + return Object.values(PLAYABLE_LESSONS) +} + +export function countGradableQuestions(lesson: LessonDefinition): number { + return getGradableQuestions(lesson).length +} + +export function getLessonMetadata(activityKey: string): Pick | null { + const lesson = PLAYABLE_LESSONS[activityKey] + if (!lesson) return null + return { id: lesson.id, version: lesson.version, title: lesson.title } +} diff --git a/src/gold-land/lessons/lesson.sanitizer.ts b/src/gold-land/lessons/lesson.sanitizer.ts new file mode 100644 index 0000000..0f9d0cd --- /dev/null +++ b/src/gold-land/lessons/lesson.sanitizer.ts @@ -0,0 +1,48 @@ +import type { + LessonDefinition, + LessonStep, + SanitizedLessonDefinition, + SanitizedLessonStep, +} from './lesson.types.js' + +const STRIP_KEYS = new Set([ + 'correctAnswerId', + 'correctAnswer', + 'correctOption', + 'answerKey', + 'correctOrder', +]) + +function sanitizeStep(step: LessonStep): SanitizedLessonStep { + const copy = { ...step } as Record + for (const key of STRIP_KEYS) { + delete copy[key] + } + return copy as SanitizedLessonStep +} + +export function sanitizeLesson(lesson: LessonDefinition): SanitizedLessonDefinition { + return { + id: lesson.id, + version: lesson.version, + title: lesson.title, + steps: lesson.steps.map(sanitizeStep), + } +} + +/** حذف کلیدهای حساس از هر آبجکت — برای اطمینان در response */ +export function stripSensitiveFields(value: T): T { + if (value === null || typeof value !== 'object') return value + + if (Array.isArray(value)) { + return value.map((item) => stripSensitiveFields(item)) as T + } + + const result: Record = {} + for (const [key, val] of Object.entries(value as Record)) { + if (STRIP_KEYS.has(key)) continue + if (['score', 'stars', 'rewardXp', 'rewardCoins'].includes(key)) continue + result[key] = stripSensitiveFields(val) + } + return result as T +} diff --git a/src/gold-land/lessons/lesson.types.ts b/src/gold-land/lessons/lesson.types.ts new file mode 100644 index 0000000..c73990f --- /dev/null +++ b/src/gold-land/lessons/lesson.types.ts @@ -0,0 +1,109 @@ +export type LessonStepType = 'story' | 'single_choice' | 'true_false' | 'sort' | 'summary' + +export interface LessonOption { + id: string + label: string +} + +export interface LessonStepBase { + id: string + type: LessonStepType + title?: string +} + +export interface StoryStep extends LessonStepBase { + type: 'story' + body: string + imageKey?: string +} + +export interface SingleChoiceStep extends LessonStepBase { + type: 'single_choice' + question: string + options: LessonOption[] + correctAnswerId: string +} + +export interface TrueFalseStep extends LessonStepBase { + type: 'true_false' + question: string + correctAnswerId: 'true' | 'false' +} + +export interface SortStep extends LessonStepBase { + type: 'sort' + question: string + items: LessonOption[] + correctOrder: string[] +} + +export interface SummaryStep extends LessonStepBase { + type: 'summary' + body: string + highlight?: string +} + +export type LessonStep = + | StoryStep + | SingleChoiceStep + | TrueFalseStep + | SortStep + | SummaryStep + +export interface LessonDefinition { + id: string + version: number + title: string + steps: LessonStep[] +} + +export interface GradableQuestion { + questionId: string + step: SingleChoiceStep | TrueFalseStep | SortStep +} + +/** پاسخ single_choice / true_false */ +export interface ChoiceLessonAnswer { + questionId: string + answerId: string +} + +/** پاسخ sort */ +export interface SortLessonAnswer { + questionId: string + orderedItemIds: string[] +} + +export type LessonSubmissionAnswer = ChoiceLessonAnswer | SortLessonAnswer + +export interface GradeLessonResult { + score: number + correctCount: number + totalQuestions: number +} + +export type SanitizedLessonStep = + | Omit + | Omit + | Omit + | Omit + | Omit + +export interface SanitizedLessonDefinition { + id: string + version: number + title: string + steps: SanitizedLessonStep[] +} + +export function isSortLessonAnswer( + answer: LessonSubmissionAnswer, +): answer is SortLessonAnswer { + return 'orderedItemIds' in answer && Array.isArray(answer.orderedItemIds) +} + +export function isChoiceLessonAnswer( + answer: LessonSubmissionAnswer, +): answer is ChoiceLessonAnswer { + return 'answerId' in answer && typeof answer.answerId === 'string' +} diff --git a/src/gold-land/lessons/needs-and-wants.lesson.ts b/src/gold-land/lessons/needs-and-wants.lesson.ts new file mode 100644 index 0000000..7be528b --- /dev/null +++ b/src/gold-land/lessons/needs-and-wants.lesson.ts @@ -0,0 +1,76 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const needsAndWantsLesson: LessonDefinition = { + id: 'needs-and-wants', + version: 1, + title: 'نیاز و خواسته', + steps: [ + { + id: 'needs-story', + type: 'story', + title: 'بودجهٔ محدود', + body: 'تو ۱۰ سکه داری. هم آب و غذا لازم داری، هم یک اسباب‌بازی جذاب دیده‌ای. همهٔ چیزها یک‌اندازه مهم نیستند!', + imageKey: 'cart', + }, + { + id: 'needs-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: '«نیاز» یعنی چه؟', + options: [ + { id: 'option-a', label: 'چیزی که برای زندگی سالم لازم است' }, + { id: 'option-b', label: 'هر چیزی که دوست داریم داشته باشیم' }, + { id: 'option-c', label: 'فقط چیزهای گران' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'needs-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'خواسته‌ها بد هستند، ولی باید بعد از نیازها به آن‌ها فکر کنیم.', + correctAnswerId: 'true', + }, + { + id: 'needs-q3', + type: 'sort', + title: 'سؤال ۳', + question: 'از مهم‌ترین (بالا) به کم‌اهمیت‌تر (پایین) مرتب کن:', + items: [ + { id: 'water', label: 'آب آشامیدنی' }, + { id: 'medicine', label: 'داروی ضروری' }, + { id: 'shoes', label: 'کفش مناسب' }, + { id: 'toy', label: 'اسباب‌بازی' }, + { id: 'crown', label: 'تاج تزئینی' }, + { id: 'food', label: 'خوراکی ضروری' }, + ], + correctOrder: ['water', 'food', 'medicine', 'shoes', 'toy', 'crown'], + }, + { + id: 'needs-q4', + type: 'single_choice', + title: 'سؤال ۴', + question: 'با بودجهٔ محدود اول باید چه کار کنیم؟', + options: [ + { id: 'option-a', label: 'اول نیازها را برطرف کنیم' }, + { id: 'option-b', label: 'اول گران‌ترین خواسته را بخریم' }, + { id: 'option-c', label: 'همهٔ سکه‌ها را یک‌جا خرج کنیم' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'needs-q5', + type: 'true_false', + title: 'سؤال ۵', + question: 'تاج تزئینی معمولاً یک خواسته است، نه یک نیاز.', + correctAnswerId: 'true', + }, + { + id: 'needs-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'نیازها اولویت دارند. خواسته‌ها هم خوبند، ولی وقتی بودجه کافی داری!', + highlight: 'اول نیاز، بعد خواسته.', + }, + ], +} diff --git a/src/gold-land/lessons/saving.lesson.ts b/src/gold-land/lessons/saving.lesson.ts new file mode 100644 index 0000000..818394f --- /dev/null +++ b/src/gold-land/lessons/saving.lesson.ts @@ -0,0 +1,73 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const savingLesson: LessonDefinition = { + id: 'saving', + version: 1, + title: 'پس‌انداز', + steps: [ + { + id: 'saving-story', + type: 'story', + title: 'رویا و دوچرخه', + body: 'سارا برای دوچرخهٔ جدید ۵۰ سکه لازم داشت. همهٔ سکه‌هایش را برای خوراکی و اسباب‌بازی خرج کرد و برای دوچرخه چیزی نماند.', + imageKey: 'piggy', + }, + { + id: 'saving-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: 'پس‌انداز یعنی چه؟', + options: [ + { id: 'option-a', label: 'همهٔ پول را یک‌جا خرج کردن' }, + { id: 'option-b', label: 'بخشی از پول را برای بعد کنار گذاشتن' }, + { id: 'option-c', label: 'پول را به دیگران هدیه دادن' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'saving-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'داشتن یک هدف مالی به ما کمک می‌کند بهتر پس‌انداز کنیم.', + correctAnswerId: 'true', + }, + { + id: 'saving-q3', + type: 'single_choice', + title: 'سؤال ۳', + question: 'سارا چه اشتباهی کرد؟', + options: [ + { id: 'option-a', label: 'برای هدفش برنامه نداشت و همه را خرج کرد' }, + { id: 'option-b', label: 'دوچرخه را خیلی زود خرید' }, + { id: 'option-c', label: 'اصلاً پول دریافت نکرد' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'saving-q4', + type: 'true_false', + title: 'سؤال ۴', + question: 'حتی با مبلغ کم هم می‌توان به‌صورت منظم پس‌انداز کرد.', + correctAnswerId: 'true', + }, + { + id: 'saving-q5', + type: 'single_choice', + title: 'سؤال ۵', + question: 'تفاوت «خرج امروز» و «هدف آینده» چیست؟', + options: [ + { id: 'option-a', label: 'هدف آینده چیزی است که برایش برنامه می‌ریزیم' }, + { id: 'option-b', label: 'هیچ تفاوتی ندارند' }, + { id: 'option-c', label: 'خرج امروز همیشه مهم‌تر از هدف است' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'saving-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'با هدف مشخص و پس‌انداز کوچک و منظم، می‌توانی به آرزوهایت برسی.', + highlight: 'اول نیازها و هدف، بعد خواسته‌های کوچک!', + }, + ], +} diff --git a/src/gold-land/lessons/what-is-gold.lesson.ts b/src/gold-land/lessons/what-is-gold.lesson.ts new file mode 100644 index 0000000..e8ad995 --- /dev/null +++ b/src/gold-land/lessons/what-is-gold.lesson.ts @@ -0,0 +1,73 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const whatIsGoldLesson: LessonDefinition = { + id: 'what-is-gold', + version: 1, + title: 'طلا چیه؟', + steps: [ + { + id: 'gold-story', + type: 'story', + title: 'فلز درخشان', + body: 'طلا فلزی کمیاب است که سال‌ها در جواهرات، سکه و صنعت استفاده شده. براق است و زنگ نمی‌زند.', + imageKey: 'gold-bar', + }, + { + id: 'gold-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: 'چرا طلا با گرم اندازه‌گیری می‌شود؟', + options: [ + { id: 'option-a', label: 'چون واحد استاندارد وزن طلا است' }, + { id: 'option-b', label: 'چون طلا همیشه مربع است' }, + { id: 'option-c', label: 'چون فقط در ایران طلا داریم' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'gold-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'طلا کمیاب و بادوام است.', + correctAnswerId: 'true', + }, + { + id: 'gold-q3', + type: 'single_choice', + title: 'سؤال ۳', + question: 'کدام مورد یک کاربرد طلا است؟', + options: [ + { id: 'option-a', label: 'زیورآلات و صنعت' }, + { id: 'option-b', label: 'آبیاری مزارع بدون آب' }, + { id: 'option-c', label: 'جایگزین خواب شب' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'gold-q4', + type: 'true_false', + title: 'سؤال ۴', + question: 'قیمت طلا همیشه ثابت می‌ماند و هرگز تغییر نمی‌کند.', + correctAnswerId: 'false', + }, + { + id: 'gold-q5', + type: 'single_choice', + title: 'سؤال ۵', + question: 'کدام جمله دربارهٔ طلا درست‌تر است؟', + options: [ + { id: 'option-a', label: 'طلا همیشه سود قطعی می‌دهد' }, + { id: 'option-b', label: 'طلا دارایی با ارزش است که قیمتش می‌تواند تغییر کند' }, + { id: 'option-c', label: 'طلا فقط برای بازی است' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'gold-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'طلا فلزی باارزش و شناخته‌شده است، اما قیمتش ثابت نیست.', + highlight: 'طلا را بشناس، ولی فکر کن قیمتش همیشه یکسان نمی‌ماند.', + }, + ], +} diff --git a/src/gold-land/lessons/what-is-money.lesson.ts b/src/gold-land/lessons/what-is-money.lesson.ts new file mode 100644 index 0000000..558b6f6 --- /dev/null +++ b/src/gold-land/lessons/what-is-money.lesson.ts @@ -0,0 +1,73 @@ +import type { LessonDefinition } from './lesson.types.js' + +export const whatIsMoneyLesson: LessonDefinition = { + id: 'what-is-money', + version: 1, + title: 'پول چیه؟', + steps: [ + { + id: 'money-story', + type: 'story', + title: 'نانوا و باغبان', + body: 'نانوا نان دارد ولی سیب می‌خواهد. باغبان سیب دارد ولی نان می‌خواهد. مبادله مستقیم همیشه آسان نیست!', + imageKey: 'coin', + }, + { + id: 'money-q1', + type: 'single_choice', + title: 'سؤال ۱', + question: 'پول بیشتر به ما در چه کاری کمک می‌کند؟', + options: [ + { id: 'option-a', label: 'مبادله آسان‌تر کالاها' }, + { id: 'option-b', label: 'پختن نان بدون آرد' }, + { id: 'option-c', label: 'رشد درخت بدون آب' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'money-q2', + type: 'true_false', + title: 'سؤال ۲', + question: 'پول وسیله‌ای است که به ما کمک می‌کند ارزش چیزها را با هم مقایسه کنیم.', + correctAnswerId: 'true', + }, + { + id: 'money-q3', + type: 'single_choice', + title: 'سؤال ۳', + question: 'چرا مبادله کالا به کالا گاهی سخت است؟', + options: [ + { id: 'option-a', label: 'چون همه همیشه همان چیز را می‌خواهند' }, + { id: 'option-b', label: 'چون پول رنگ دارد' }, + { id: 'option-c', label: 'چون فقط بزرگسالان مبادله می‌کنند' }, + ], + correctAnswerId: 'option-a', + }, + { + id: 'money-q4', + type: 'true_false', + title: 'سؤال ۴', + question: 'می‌توانیم بخشی از پول را برای آینده نگه داریم.', + correctAnswerId: 'true', + }, + { + id: 'money-q5', + type: 'single_choice', + title: 'سؤال ۵', + question: 'کدام جمله دربارهٔ پول درست‌تر است؟', + options: [ + { id: 'option-a', label: 'پول فقط برای خرید اسباب‌بازی است' }, + { id: 'option-b', label: 'پول وسیلهٔ مبادله و نگه‌داری ارزش است' }, + { id: 'option-c', label: 'پول همیشه جایگزین کار سخت می‌شود' }, + ], + correctAnswerId: 'option-b', + }, + { + id: 'money-summary', + type: 'summary', + title: 'جمع‌بندی', + body: 'پول به ما کمک می‌کند راحت‌تر خرید کنیم و برای آینده پس‌انداز کنیم.', + highlight: 'پول وسیلهٔ مبادله است، نه فقط یک برگه یا سکه!', + }, + ], +} diff --git a/src/gold-land/levels/level.types.ts b/src/gold-land/levels/level.types.ts new file mode 100644 index 0000000..42892f2 --- /dev/null +++ b/src/gold-land/levels/level.types.ts @@ -0,0 +1,75 @@ +import type { MeResponse } from '../../shared/types/user.types' +import type { sanitizeLesson } from '../lessons/lesson.sanitizer' +import type { LessonSubmissionAnswer } from '../lessons/lesson.types' + +export type LevelStatus = + | 'locked' + | 'available' + | 'in_progress' + | 'completed' + | 'coming_soon' + +export interface LevelProgressRow { + user_id: string + level_id: number + best_score: number + stars: number + attempts_count: number + started_at: string | null + completed_at: string | null + updated_at: string +} + +export interface CompleteLevelSubmission { + answers: LessonSubmissionAnswer[] + durationMs?: number +} + +export interface LevelItemResponse { + id: number + order: number + slug: string + title: string + description: string + status: LevelStatus + stars: 0 | 1 | 2 | 3 + bestScore: number + activityType: string + activityKey: string + activityRoute?: string + rewardXp: number + rewardCoins: number +} + +export interface LevelDetailResponse { + level: Pick< + LevelItemResponse, + | 'id' + | 'order' + | 'slug' + | 'title' + | 'description' + | 'status' + | 'stars' + | 'bestScore' + | 'activityType' + | 'activityKey' + | 'activityRoute' + > + activity: { + type: string + lesson?: ReturnType + } | null +} + +export interface CompleteLevelResponse { + levelId: number + score: number + stars: 0 | 1 | 2 | 3 + previousStars: 0 | 1 | 2 | 3 + isFirstCompletion: boolean + bestScoreImproved: boolean + reward: { xp: number; coins: number } + unlockedLevelId: number | null + me: MeResponse +} diff --git a/src/gold-land/levels/levels.ts b/src/gold-land/levels/levels.ts new file mode 100644 index 0000000..083ad30 --- /dev/null +++ b/src/gold-land/levels/levels.ts @@ -0,0 +1,182 @@ +export type ActivityType = 'lesson' | 'game' | 'challenge' | 'final_exam' + +export interface GoldLandLevelDefinition { + id: number + order: number + slug: string + title: string + description: string + activityType: ActivityType + activityKey: string + rewardXp: number + rewardCoins: number + isActive: boolean + /** مسیر فعالیت در فرانت (درس/بازی) */ + activityRoute?: string +} + +export const GOLD_LAND_LEVELS: GoldLandLevelDefinition[] = [ + { + id: 1, + order: 1, + slug: 'what-is-money', + title: 'پول چیه؟', + description: 'با مفهوم پول و کاربرد آن آشنا شو.', + activityType: 'lesson', + activityKey: 'what-is-money', + rewardXp: 40, + rewardCoins: 15, + isActive: true, + }, + { + id: 2, + order: 2, + slug: 'saving', + title: 'پس‌انداز', + description: 'یاد بگیر چطور برای هدف‌هایت پس‌انداز کنی.', + activityType: 'lesson', + activityKey: 'saving', + rewardXp: 40, + rewardCoins: 15, + isActive: true, + }, + { + id: 3, + order: 3, + slug: 'needs-and-wants', + title: 'نیاز و خواسته', + description: 'تفاوت نیاز و خواسته را یاد بگیر.', + activityType: 'lesson', + activityKey: 'needs-and-wants', + rewardXp: 45, + rewardCoins: 18, + isActive: true, + }, + { + id: 4, + order: 4, + slug: 'what-is-gold', + title: 'طلا چیه؟', + description: 'با طلا به‌عنوان دارایی آشنا شو.', + activityType: 'lesson', + activityKey: 'what-is-gold', + rewardXp: 40, + rewardCoins: 15, + isActive: true, + }, + { + id: 5, + order: 5, + slug: 'gold-market', + title: 'بازار طلا', + description: 'بازار طلا را بشناس.', + activityType: 'lesson', + activityKey: 'gold-market', + rewardXp: 50, + rewardCoins: 20, + isActive: true, + }, + { + id: 6, + order: 6, + slug: 'inflation', + title: 'تورم', + description: 'تورم چیست و چطور بر پول تو اثر می‌گذارد؟', + activityType: 'lesson', + activityKey: 'inflation', + rewardXp: 40, + rewardCoins: 15, + isActive: true, + activityRoute: '/lessons/inflation', + }, + { + id: 7, + order: 7, + slug: 'budgeting', + title: 'بودجه‌بندی', + description: 'پولت را هوشمندانه تقسیم کن.', + activityType: 'game', + activityKey: 'budgeting', + rewardXp: 45, + rewardCoins: 18, + isActive: false, + }, + { + id: 8, + order: 8, + slug: 'risk-and-return', + title: 'ریسک و بازده', + description: 'ریسک و پاداش در سرمایه‌گذاری.', + activityType: 'challenge', + activityKey: 'risk-return', + rewardXp: 50, + rewardCoins: 20, + isActive: false, + }, + { + id: 9, + order: 9, + slug: 'portfolio', + title: 'سبد سرمایه‌گذاری', + description: 'یک سبد متنوع بساز.', + activityType: 'game', + activityKey: 'portfolio', + rewardXp: 50, + rewardCoins: 20, + isActive: false, + }, + { + id: 10, + order: 10, + slug: 'compound-interest', + title: 'سود مرکب', + description: 'قدرت سود مرکب را ببین.', + activityType: 'lesson', + activityKey: 'compound-interest', + rewardXp: 45, + rewardCoins: 18, + isActive: false, + }, + { + id: 11, + order: 11, + slug: 'smart-shopping', + title: 'خرید هوشمند', + description: 'هوشمندانه خرید کن.', + activityType: 'game', + activityKey: 'smart-shop', + rewardXp: 45, + rewardCoins: 18, + isActive: false, + }, + { + id: 12, + order: 12, + slug: 'financial-goal', + title: 'هدف مالی', + description: 'برنامه مالی خودت را بساز.', + activityType: 'challenge', + activityKey: 'financial-goal', + rewardXp: 50, + rewardCoins: 20, + isActive: false, + }, + { + id: 13, + order: 13, + slug: 'castle-exam', + title: 'آزمون قلعه', + description: 'آزمون نهایی سرزمین طلا.', + activityType: 'final_exam', + activityKey: 'castle-exam', + rewardXp: 100, + rewardCoins: 50, + isActive: false, + }, +] + +export function getLevelDefinition(levelId: number) { + return GOLD_LAND_LEVELS.find((level) => level.id === levelId) +} + +export const GOLD_LAND_MAX_STARS = GOLD_LAND_LEVELS.length * 3 diff --git a/src/gold-land/services/progression.service.ts b/src/gold-land/services/progression.service.ts new file mode 100644 index 0000000..7143a13 --- /dev/null +++ b/src/gold-land/services/progression.service.ts @@ -0,0 +1,419 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common' +import { DatabaseService } from '../../database/database.service' +import type { UserState } from '../../shared/types/user.types' +import { UsersService } from '../../users/users.service' +import { ProgressionError } from '../exceptions/progression.exception' +import { + getLevelDefinition, + GOLD_LAND_LEVELS, + GOLD_LAND_MAX_STARS, +} from '../levels/levels' +import type { + CompleteLevelResponse, + CompleteLevelSubmission, + LevelDetailResponse, + LevelItemResponse, + LevelProgressRow, + LevelStatus, +} from '../levels/level.types' +import { loadLessonForLevel } from '../lessons/lesson.loader' +import { + gradeLessonSubmission, + LessonGradingError, + validateDurationMs, +} from '../lessons/lesson.grader' +import { sanitizeLesson, stripSensitiveFields } from '../lessons/lesson.sanitizer' +import { RewardService } from './reward.service' + +export function starsFromScore(score: number): 0 | 1 | 2 | 3 { + if (score < 60) return 0 + if (score < 75) return 1 + if (score < 90) return 2 + return 3 +} + +export function isLevelUnlocked( + levelId: number, + rowsById: Map, +): boolean { + if (levelId === 1) return true + const prevStars = rowsById.get(levelId - 1)?.stars ?? 0 + return prevStars >= 1 +} + +export function resolveLevelStatus( + levelId: number, + progress: LevelProgressRow | undefined, + rowsById: Map, +): LevelStatus { + const definition = getLevelDefinition(levelId) + if (!definition) return 'locked' + + if ((progress?.stars ?? 0) > 0) return 'completed' + if (progress?.started_at && !progress.completed_at) return 'in_progress' + + if (!isLevelUnlocked(levelId, rowsById)) return 'locked' + + if (!definition.isActive) return 'coming_soon' + + return 'available' +} + +function mapStatusToLegacy(status: LevelStatus): UserState['mapLevels'][number]['status'] { + switch (status) { + case 'completed': + return 'completed' + case 'in_progress': + return 'current' + case 'available': + return 'unlocked' + case 'coming_soon': + return 'locked' + default: + return 'locked' + } +} + +@Injectable() +export class GoldLandProgressionService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(forwardRef(() => UsersService)) + private readonly users: UsersService, + @Inject(forwardRef(() => RewardService)) + private readonly rewards: RewardService, + ) {} + + private getProgressRow(userId: string, levelId: number): LevelProgressRow | undefined { + return this.database + .getDb() + .prepare('SELECT * FROM user_level_progress WHERE user_id = ? AND level_id = ?') + .get(userId, levelId) as LevelProgressRow | undefined + } + + private getAllProgressRows(userId: string): LevelProgressRow[] { + return this.database + .getDb() + .prepare('SELECT * FROM user_level_progress WHERE user_id = ?') + .all(userId) as LevelProgressRow[] + } + + private migrateFromLegacyState(userId: string): void { + const existing = this.getAllProgressRows(userId) + if (existing.length > 0) return + + const state = this.users.getUserState(userId) + const now = new Date().toISOString() + const db = this.database.getDb() + const insert = db.prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, + started_at, completed_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `) + + this.database.transaction(() => { + for (const legacy of state.mapLevels) { + // Only migrate levels the user actually passed (≥1 star). + if (legacy.stars < 1) continue + insert.run( + userId, + legacy.id, + legacy.stars > 0 ? legacy.stars * 30 : 0, + legacy.stars, + legacy.stars > 0 ? 1 : 0, + legacy.stars > 0 ? now : null, + legacy.status === 'completed' && legacy.stars > 0 ? now : null, + now, + ) + } + }) + } + + private syncLegacyMapLevels(state: UserState, items: LevelItemResponse[]): void { + state.mapLevels = items.map((item) => ({ + id: item.id, + title: item.title, + stars: item.stars, + maxStars: 3, + status: mapStatusToLegacy(item.status), + lessonRoute: item.activityRoute, + x: 0, + y: 0, + })) + state.totalStars = items.reduce((sum, item) => sum + item.stars, 0) + state.maxStars = GOLD_LAND_MAX_STARS + } + + buildLevelItems(userId: string): LevelItemResponse[] { + this.migrateFromLegacyState(userId) + const rows = this.getAllProgressRows(userId) + const byId = new Map(rows.map((row) => [row.level_id, row])) + + return GOLD_LAND_LEVELS.map((definition) => { + const progress = byId.get(definition.id) + const status = resolveLevelStatus(definition.id, progress, byId) + + return { + id: definition.id, + order: definition.order, + slug: definition.slug, + title: definition.title, + description: definition.description, + status, + stars: (progress?.stars ?? 0) as 0 | 1 | 2 | 3, + bestScore: progress?.best_score ?? 0, + activityType: definition.activityType, + activityKey: definition.activityKey, + activityRoute: definition.activityRoute, + rewardXp: definition.rewardXp, + rewardCoins: definition.rewardCoins, + } + }) + } + + syncUserProgressionState(userId: string): UserState { + const state = this.users.getUserState(userId) + const items = this.buildLevelItems(userId) + this.syncLegacyMapLevels(state, items) + return this.users.saveUserState(state) + } + + getGoldLandProgress(userId: string) { + this.syncUserProgressionState(userId) + const levels = this.buildLevelItems(userId) + const totalStars = levels.reduce((sum, level) => sum + level.stars, 0) + const currentLevelId = + levels.find((level) => level.status === 'in_progress')?.id ?? + levels.find((level) => level.status === 'available')?.id ?? + null + + return { + levels, + totalStars, + maxStars: GOLD_LAND_MAX_STARS, + currentLevelId, + } + } + + getGoldLandLevel(userId: string, levelId: number): LevelDetailResponse { + const levels = this.buildLevelItems(userId) + const level = levels.find((item) => item.id === levelId) + if (!level) throw new ProgressionError(404, 'مرحله پیدا نشد') + + const definition = getLevelDefinition(levelId) + let activity: LevelDetailResponse['activity'] = null + + const canAccessContent = + level.status === 'available' || + level.status === 'in_progress' || + level.status === 'completed' + + if (canAccessContent && definition?.activityType === 'lesson') { + const lesson = loadLessonForLevel(definition.activityKey) + if (lesson) { + activity = { + type: 'lesson', + lesson: stripSensitiveFields(sanitizeLesson(lesson)), + } + } + } + + return stripSensitiveFields({ + level: { + id: level.id, + order: level.order, + slug: level.slug, + title: level.title, + description: level.description, + status: level.status, + stars: level.stars, + bestScore: level.bestScore, + activityType: level.activityType, + activityKey: level.activityKey, + activityRoute: level.activityRoute, + }, + activity, + }) + } + + startLevel(userId: string, levelId: number) { + this.assertLevelPlayable(userId, levelId) + + const definition = getLevelDefinition(levelId) + if (!definition?.isActive) { + throw new ProgressionError(403, 'این مرحله به‌زودی باز می‌شود') + } + + const now = new Date().toISOString() + const existing = this.getProgressRow(userId, levelId) + const db = this.database.getDb() + + if (existing?.completed_at) { + db.prepare(` + UPDATE user_level_progress + SET attempts_count = attempts_count + 1, started_at = ?, updated_at = ? + WHERE user_id = ? AND level_id = ? + `).run(now, now, userId, levelId) + } else if (existing) { + db.prepare(` + UPDATE user_level_progress + SET started_at = COALESCE(started_at, ?), updated_at = ? + WHERE user_id = ? AND level_id = ? + `).run(now, now, userId, levelId) + } else { + db.prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, started_at, updated_at + ) VALUES (?, ?, 0, 0, 1, ?, ?) + `).run(userId, levelId, now, now) + } + + return { levelId, status: 'in_progress' as const } + } + + completeLevel( + userId: string, + levelId: number, + submission: CompleteLevelSubmission, + ): CompleteLevelResponse { + this.assertLevelPlayable(userId, levelId) + + const definition = getLevelDefinition(levelId) + if (!definition?.isActive) { + throw new ProgressionError(403, 'این مرحله هنوز آماده نیست') + } + + const score = this.gradeSubmission(levelId, submission) + const newStars = starsFromScore(score) + if (newStars === 0) { + throw new ProgressionError(400, 'امتیاز کافی برای تکمیل مرحله نبود. دوباره تلاش کن!') + } + + const existing = this.getProgressRow(userId, levelId) + const previousStars = (existing?.stars ?? 0) as 0 | 1 | 2 | 3 + const previousBestScore = existing?.best_score ?? 0 + const bestScore = Math.max(previousBestScore, score) + const bestScoreImproved = score > previousBestScore + const finalStars = Math.max(previousStars, newStars) as 0 | 1 | 2 | 3 + const isFirstCompletion = !existing?.completed_at + const now = new Date().toISOString() + + return this.database.transaction(() => { + const db = this.database.getDb() + + if (existing) { + db.prepare(` + UPDATE user_level_progress + SET best_score = ?, stars = ?, attempts_count = attempts_count + 1, + completed_at = COALESCE(completed_at, ?), updated_at = ? + WHERE user_id = ? AND level_id = ? + `).run(bestScore, finalStars, now, now, userId, levelId) + } else { + db.prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, + started_at, completed_at, updated_at + ) VALUES (?, ?, ?, ?, 1, ?, ?, ?) + `).run(userId, levelId, bestScore, finalStars, now, now, now) + } + + const starDelta = Math.max(0, finalStars - previousStars) + + let totalXp = 0 + let totalCoins = 0 + + if (isFirstCompletion && finalStars > 0) { + const first = this.rewards.grantReward({ + userId, + sourceType: 'level', + sourceId: String(levelId), + rewardKey: `level:${levelId}:first-completion`, + xpDelta: definition.rewardXp, + coinsDelta: definition.rewardCoins, + }) + totalXp += first.xpDelta + totalCoins += first.coinsDelta + } + + if (starDelta > 0) { + for (let star = previousStars + 1; star <= finalStars; star += 1) { + const starReward = this.rewards.grantReward({ + userId, + sourceType: 'level', + sourceId: String(levelId), + rewardKey: `level:${levelId}:star:${star}`, + xpDelta: 5, + coinsDelta: 3, + }) + totalXp += starReward.xpDelta + totalCoins += starReward.coinsDelta + } + } + + const state = this.users.getUserState(userId) + const items = this.buildLevelItems(userId) + this.syncLegacyMapLevels(state, items) + + if (levelId === 6 && !state.completedLessons.includes('inflation')) { + state.completedLessons.push('inflation') + const achievement = state.achievements.find((a) => a.id === 'inflation') + if (achievement) achievement.unlocked = true + } + + this.users.saveUserState(state) + + const unlockedLevelId = + finalStars >= 1 && levelId < GOLD_LAND_LEVELS.length ? levelId + 1 : null + + return { + levelId, + score, + stars: finalStars, + previousStars, + isFirstCompletion, + bestScoreImproved, + reward: { xp: totalXp, coins: totalCoins }, + unlockedLevelId, + me: this.users.toMeResponse(state), + } + }) + } + + private assertLevelPlayable(userId: string, levelId: number): LevelItemResponse { + const items = this.buildLevelItems(userId) + const item = items.find((level) => level.id === levelId) + if (!item) throw new ProgressionError(404, 'مرحله پیدا نشد') + if (item.status === 'locked') throw new ProgressionError(403, 'این مرحله هنوز قفل است') + if (item.status === 'coming_soon') { + throw new ProgressionError(403, 'این مرحله به‌زودی باز می‌شود') + } + return item + } + + private gradeSubmission(levelId: number, submission: CompleteLevelSubmission): number { + const definition = getLevelDefinition(levelId) + if (!definition) throw new ProgressionError(404, 'مرحله پیدا نشد') + + validateDurationMs(submission.durationMs) + + if (definition.activityType === 'lesson') { + const lesson = loadLessonForLevel(definition.activityKey) + if (!lesson) { + throw new ProgressionError(403, 'محتوای این درس هنوز آماده نیست') + } + + try { + const result = gradeLessonSubmission(lesson, submission.answers ?? []) + return result.score + } catch (error) { + if (error instanceof LessonGradingError) { + throw new ProgressionError(error.statusCode, error.message) + } + throw error + } + } + + throw new ProgressionError(403, 'ارزیابی این مرحله هنوز پیاده‌سازی نشده') + } +} diff --git a/src/gold-land/services/reward.service.ts b/src/gold-land/services/reward.service.ts new file mode 100644 index 0000000..a0394af --- /dev/null +++ b/src/gold-land/services/reward.service.ts @@ -0,0 +1,78 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common' +import { DatabaseService } from '../../database/database.service' +import { addXp } from '../../shared/economy/user-economy.helpers' +import { UsersService } from '../../users/users.service' + +export interface GrantRewardInput { + userId: string + sourceType: string + sourceId: string + rewardKey: string + xpDelta: number + coinsDelta: number + metadata?: Record +} + +export interface GrantRewardResult { + granted: boolean + xpDelta: number + coinsDelta: number +} + +@Injectable() +export class RewardService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(forwardRef(() => UsersService)) + private readonly users: UsersService, + ) {} + + grantReward(input: GrantRewardInput): GrantRewardResult { + const insert = this.database.getDb().prepare(` + INSERT INTO reward_ledger ( + user_id, source_type, source_id, reward_key, + xp_delta, coins_delta, metadata_json, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `) + + return this.database.transaction(() => { + try { + insert.run( + input.userId, + input.sourceType, + input.sourceId, + input.rewardKey, + input.xpDelta, + input.coinsDelta, + input.metadata ? JSON.stringify(input.metadata) : null, + new Date().toISOString(), + ) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + if (message.includes('UNIQUE constraint failed')) { + return { granted: false, xpDelta: 0, coinsDelta: 0 } + } + throw error + } + + const state = this.users.getUserState(input.userId) + if (input.xpDelta > 0) addXp(state, input.xpDelta) + if (input.coinsDelta > 0) state.coins += input.coinsDelta + this.users.saveUserState(state) + + return { + granted: true, + xpDelta: input.xpDelta, + coinsDelta: input.coinsDelta, + } + }) + } + + hasReward(userId: string, rewardKey: string): boolean { + const row = this.database + .getDb() + .prepare('SELECT 1 FROM reward_ledger WHERE user_id = ? AND reward_key = ?') + .get(userId, rewardKey) + return Boolean(row) + } +} diff --git a/src/health/health.controller.ts b/src/health/health.controller.ts new file mode 100644 index 0000000..828f3a5 --- /dev/null +++ b/src/health/health.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Get } from '@nestjs/common' +import { Public } from '../common/decorators/public.decorator' + +@Public() +@Controller() +export class HealthController { + @Get('health') + getHealth() { + return { + status: 'ok', + service: 'child-gold-land-backend', + runtime: 'nest', + } + } +} diff --git a/src/health/health.module.ts b/src/health/health.module.ts new file mode 100644 index 0000000..c40b3b7 --- /dev/null +++ b/src/health/health.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common' +import { HealthController } from './health.controller' + +@Module({ + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..d51af9b --- /dev/null +++ b/src/main.ts @@ -0,0 +1,43 @@ +import 'reflect-metadata' +import { ValidationPipe } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { NestFactory } from '@nestjs/core' +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify' +import { AppModule } from './app.module' +import { ApiExceptionFilter } from './common/filters/api-exception.filter' + +async function bootstrap() { + const app = await NestFactory.create( + AppModule, + new FastifyAdapter(), + ) + + app.enableShutdownHooks() + + const config = app.get(ConfigService) + + app.enableCors({ + origin: config.get('CORS_ORIGIN'), + credentials: true, + }) + + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + }), + ) + + app.useGlobalFilters(new ApiExceptionFilter()) + + const port = config.get('PORT') ?? 3001 + const host = config.get('HOST') ?? '0.0.0.0' + + await app.listen(port, host) +} + +bootstrap().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/src/market/dto/update-market-preferences.dto.ts b/src/market/dto/update-market-preferences.dto.ts new file mode 100644 index 0000000..a86b314 --- /dev/null +++ b/src/market/dto/update-market-preferences.dto.ts @@ -0,0 +1,9 @@ +import { IsIn } from 'class-validator' +import type { TimeRange } from '../../shared/types/user.types' + +const TIME_RANGES: TimeRange[] = ['1d', '7d', '30d', '90d', '1y'] + +export class UpdateMarketPreferencesDto { + @IsIn(TIME_RANGES) + range!: TimeRange +} diff --git a/src/market/market.controller.ts b/src/market/market.controller.ts new file mode 100644 index 0000000..871ae3e --- /dev/null +++ b/src/market/market.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Get, HttpCode, Inject, Patch, Query } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { UpdateMarketPreferencesDto } from './dto/update-market-preferences.dto' +import { MarketService } from './market.service' + +@Controller('api/market') +export class MarketController { + constructor(@Inject(MarketService) private readonly market: MarketService) {} + + @Get('gold') + getMarketGold(@Query('range') range?: string) { + return this.market.getMarketGold(range ?? '1d') + } + + @HttpCode(200) + @Patch('preferences') + setMarketRange( + @CurrentUser() userId: string, + @Body(strictBodyPipe(UpdateMarketPreferencesDto)) dto: UpdateMarketPreferencesDto, + ) { + return this.market.setMarketRange(userId, dto.range) + } +} diff --git a/src/market/market.module.ts b/src/market/market.module.ts new file mode 100644 index 0000000..a3741ad --- /dev/null +++ b/src/market/market.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { MarketController } from './market.controller' +import { MarketService } from './market.service' + +@Module({ + imports: [DatabaseModule, UsersModule], + controllers: [MarketController], + providers: [MarketService], +}) +export class MarketModule {} diff --git a/src/market/market.service.ts b/src/market/market.service.ts new file mode 100644 index 0000000..e08c95a --- /dev/null +++ b/src/market/market.service.ts @@ -0,0 +1,45 @@ +import { Inject, Injectable } from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { + DAILY_CHANGE, + GOLD_PRICE, + PRICE_HISTORY, +} from '../shared/catalog/market.constants' +import type { MeResponse, TimeRange } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +export interface MarketGoldResponse { + currentPrice: number + dailyChangePercent: number + range: TimeRange + history: number[] + updatedAt: string +} + +@Injectable() +export class MarketService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(UsersService) private readonly users: UsersService, + ) {} + + getMarketGold(range: string = '1d'): MarketGoldResponse { + const history = PRICE_HISTORY[range as TimeRange] ?? PRICE_HISTORY['1d'] + return { + currentPrice: GOLD_PRICE, + dailyChangePercent: DAILY_CHANGE, + range: range as TimeRange, + history, + updatedAt: new Date().toISOString(), + } + } + + setMarketRange(userId: string, range: TimeRange): MeResponse { + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + state.marketSelectedRange = range + this.users.saveUserState(state) + return this.users.toMeResponse(state) + }) + } +} diff --git a/src/progression/levels.ts b/src/progression/levels.ts new file mode 100644 index 0000000..7f68291 --- /dev/null +++ b/src/progression/levels.ts @@ -0,0 +1,37 @@ +export type ActivityType = 'lesson' | 'game' | 'challenge' | 'final_exam' + +export interface GoldLandLevelDefinition { + id: number + order: number + slug: string + title: string + description: string + activityType: ActivityType + activityKey: string + rewardXp: number + rewardCoins: number + isActive: boolean + activityRoute?: string +} + +export const GOLD_LAND_LEVELS: GoldLandLevelDefinition[] = [ + { id: 1, order: 1, slug: 'what-is-money', title: 'پول چیه؟', description: 'با مفهوم پول و کاربرد آن آشنا شو.', activityType: 'lesson', activityKey: 'what-is-money', rewardXp: 40, rewardCoins: 15, isActive: true }, + { id: 2, order: 2, slug: 'saving', title: 'پس‌انداز', description: 'یاد بگیر چطور برای هدف‌هایت پس‌انداز کنی.', activityType: 'lesson', activityKey: 'saving', rewardXp: 40, rewardCoins: 15, isActive: true }, + { id: 3, order: 3, slug: 'needs-and-wants', title: 'نیاز و خواسته', description: 'تفاوت نیاز و خواسته را یاد بگیر.', activityType: 'lesson', activityKey: 'needs-and-wants', rewardXp: 45, rewardCoins: 18, isActive: true }, + { id: 4, order: 4, slug: 'what-is-gold', title: 'طلا چیه؟', description: 'با طلا به‌عنوان دارایی آشنا شو.', activityType: 'lesson', activityKey: 'what-is-gold', rewardXp: 40, rewardCoins: 15, isActive: true }, + { id: 5, order: 5, slug: 'gold-market', title: 'بازار طلا', description: 'بازار طلا را بشناس.', activityType: 'lesson', activityKey: 'gold-market', rewardXp: 50, rewardCoins: 20, isActive: true }, + { id: 6, order: 6, slug: 'inflation', title: 'تورم', description: 'تورم چیست و چطور بر پول تو اثر می‌گذارد؟', activityType: 'lesson', activityKey: 'inflation', rewardXp: 40, rewardCoins: 15, isActive: true, activityRoute: '/lessons/inflation' }, + { id: 7, order: 7, slug: 'budgeting', title: 'بودجه‌بندی', description: 'پولت را هوشمندانه تقسیم کن.', activityType: 'game', activityKey: 'budgeting', rewardXp: 45, rewardCoins: 18, isActive: false }, + { id: 8, order: 8, slug: 'risk-and-return', title: 'ریسک و بازده', description: 'ریسک و پاداش در سرمایه‌گذاری.', activityType: 'challenge', activityKey: 'risk-return', rewardXp: 50, rewardCoins: 20, isActive: false }, + { id: 9, order: 9, slug: 'portfolio', title: 'سبد سرمایه‌گذاری', description: 'یک سبد متنوع بساز.', activityType: 'game', activityKey: 'portfolio', rewardXp: 50, rewardCoins: 20, isActive: false }, + { id: 10, order: 10, slug: 'compound-interest', title: 'سود مرکب', description: 'قدرت سود مرکب را ببین.', activityType: 'lesson', activityKey: 'compound-interest', rewardXp: 45, rewardCoins: 18, isActive: false }, + { id: 11, order: 11, slug: 'smart-shopping', title: 'خرید هوشمند', description: 'هوشمندانه خرید کن.', activityType: 'game', activityKey: 'smart-shop', rewardXp: 45, rewardCoins: 18, isActive: false }, + { id: 12, order: 12, slug: 'financial-goal', title: 'هدف مالی', description: 'برنامه مالی خودت را بساز.', activityType: 'challenge', activityKey: 'financial-goal', rewardXp: 50, rewardCoins: 20, isActive: false }, + { id: 13, order: 13, slug: 'castle-exam', title: 'آزمون قلعه', description: 'آزمون نهایی سرزمین طلا.', activityType: 'final_exam', activityKey: 'castle-exam', rewardXp: 100, rewardCoins: 50, isActive: false }, +] + +export function getLevelDefinition(levelId: number) { + return GOLD_LAND_LEVELS.find((level) => level.id === levelId) +} + +export const GOLD_LAND_MAX_STARS = GOLD_LAND_LEVELS.length * 3 diff --git a/src/progression/progression-sync.service.ts b/src/progression/progression-sync.service.ts new file mode 100644 index 0000000..b2d9ed9 --- /dev/null +++ b/src/progression/progression-sync.service.ts @@ -0,0 +1,180 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { + GOLD_LAND_LEVELS, + GOLD_LAND_MAX_STARS, + getLevelDefinition, +} from './levels' +import type { UserState } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +type LevelStatus = 'locked' | 'available' | 'in_progress' | 'completed' | 'coming_soon' + +interface LevelProgressRow { + user_id: string + level_id: number + best_score: number + stars: number + attempts_count: number + started_at: string | null + completed_at: string | null + updated_at: string +} + +interface LevelItemResponse { + id: number + order: number + slug: string + title: string + description: string + status: LevelStatus + stars: 0 | 1 | 2 | 3 + bestScore: number + activityType: string + activityKey: string + activityRoute?: string + rewardXp: number + rewardCoins: number +} + +@Injectable() +export class ProgressionSyncService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(forwardRef(() => UsersService)) + private readonly users: UsersService, + ) {} + + syncUserProgressionState(userId: string): UserState { + const state = this.users.getUserState(userId) + const items = this.buildLevelItems(userId) + this.syncLegacyMapLevels(state, items) + return this.users.saveUserState(state) + } + + private getProgressRow(userId: string, levelId: number): LevelProgressRow | undefined { + return this.database + .getDb() + .prepare('SELECT * FROM user_level_progress WHERE user_id = ? AND level_id = ?') + .get(userId, levelId) as LevelProgressRow | undefined + } + + private getAllProgressRows(userId: string): LevelProgressRow[] { + return this.database + .getDb() + .prepare('SELECT * FROM user_level_progress WHERE user_id = ?') + .all(userId) as LevelProgressRow[] + } + + private migrateFromLegacyState(userId: string): void { + const existing = this.getAllProgressRows(userId) + if (existing.length > 0) return + + const state = this.users.getUserState(userId) + const now = new Date().toISOString() + const db = this.database.getDb() + const insert = db.prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, + started_at, completed_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `) + + this.database.transaction(() => { + for (const legacy of state.mapLevels) { + if (legacy.stars <= 0 && legacy.status !== 'completed') continue + insert.run( + userId, + legacy.id, + legacy.stars > 0 ? legacy.stars * 30 : 0, + legacy.stars, + legacy.stars > 0 ? 1 : 0, + legacy.stars > 0 ? now : null, + legacy.status === 'completed' && legacy.stars > 0 ? now : null, + now, + ) + } + }) + } + + private isLevelUnlocked(levelId: number, rowsById: Map): boolean { + if (levelId === 1) return true + const prevStars = rowsById.get(levelId - 1)?.stars ?? 0 + return prevStars >= 1 + } + + private resolveLevelStatus( + levelId: number, + progress: LevelProgressRow | undefined, + rowsById: Map, + ): LevelStatus { + const definition = getLevelDefinition(levelId) + if (!definition) return 'locked' + + if ((progress?.stars ?? 0) > 0) return 'completed' + if (progress?.started_at && !progress.completed_at) return 'in_progress' + + if (!this.isLevelUnlocked(levelId, rowsById)) return 'locked' + + if (!definition.isActive) return 'coming_soon' + + return 'available' + } + + private buildLevelItems(userId: string): LevelItemResponse[] { + this.migrateFromLegacyState(userId) + const rows = this.getAllProgressRows(userId) + const byId = new Map(rows.map((row) => [row.level_id, row])) + + return GOLD_LAND_LEVELS.map((definition) => { + const progress = byId.get(definition.id) + const status = this.resolveLevelStatus(definition.id, progress, byId) + + return { + id: definition.id, + order: definition.order, + slug: definition.slug, + title: definition.title, + description: definition.description, + status, + stars: (progress?.stars ?? 0) as 0 | 1 | 2 | 3, + bestScore: progress?.best_score ?? 0, + activityType: definition.activityType, + activityKey: definition.activityKey, + activityRoute: definition.activityRoute, + rewardXp: definition.rewardXp, + rewardCoins: definition.rewardCoins, + } + }) + } + + private syncLegacyMapLevels(state: UserState, items: LevelItemResponse[]): void { + state.mapLevels = items.map((item) => ({ + id: item.id, + title: item.title, + stars: item.stars, + maxStars: 3, + status: this.mapStatusToLegacy(item.status), + lessonRoute: item.activityRoute, + x: 0, + y: 0, + })) + state.totalStars = items.reduce((sum, item) => sum + item.stars, 0) + state.maxStars = GOLD_LAND_MAX_STARS + } + + private mapStatusToLegacy(status: LevelStatus): UserState['mapLevels'][number]['status'] { + switch (status) { + case 'completed': + return 'completed' + case 'in_progress': + return 'current' + case 'available': + return 'unlocked' + case 'coming_soon': + return 'locked' + default: + return 'locked' + } + } +} diff --git a/src/progression/progression.module.ts b/src/progression/progression.module.ts new file mode 100644 index 0000000..00fc184 --- /dev/null +++ b/src/progression/progression.module.ts @@ -0,0 +1,11 @@ +import { Module, forwardRef } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { ProgressionSyncService } from './progression-sync.service' +import { UsersModule } from '../users/users.module' + +@Module({ + imports: [DatabaseModule, forwardRef(() => UsersModule)], + providers: [ProgressionSyncService], + exports: [ProgressionSyncService], +}) +export class ProgressionModule {} diff --git a/src/shared/catalog/achievements.constants.ts b/src/shared/catalog/achievements.constants.ts new file mode 100644 index 0000000..6299fcc --- /dev/null +++ b/src/shared/catalog/achievements.constants.ts @@ -0,0 +1,48 @@ +export interface AchievementDefinition { + id: string + title: string + description: string + icon: string +} + +export const ACHIEVEMENTS: AchievementDefinition[] = [ + { + id: 'first-gold', + title: 'اولین خرید طلا', + description: 'اولین خرید طلای خودت رو انجام دادی', + icon: '🥇', + }, + { + id: 'saver', + title: 'پس‌اندازگر کوچک', + description: '۱۰۰ هزار تومان پس‌انداز کردی', + icon: '💰', + }, + { + id: 'market', + title: 'شاگرد بازار', + description: '۵ بار در بازار معامله کردی', + icon: '📊', + }, + { + id: 'inflation', + title: 'شکست‌دهنده تورم', + description: 'درس تورم رو کامل کردی', + icon: '👾', + }, +] + +/** Fastify catalog.ts default unlocked flags — separate from catalog definitions. */ +export const DEFAULT_ACHIEVEMENT_UNLOCK_STATE: Record = { + 'first-gold': true, + saver: true, + market: false, + inflation: false, +} + +export function createDefaultAchievements() { + return ACHIEVEMENTS.map((achievement) => ({ + ...achievement, + unlocked: DEFAULT_ACHIEVEMENT_UNLOCK_STATE[achievement.id], + })) +} diff --git a/src/shared/catalog/challenge.constants.ts b/src/shared/catalog/challenge.constants.ts new file mode 100644 index 0000000..8b7ef31 --- /dev/null +++ b/src/shared/catalog/challenge.constants.ts @@ -0,0 +1,79 @@ +export interface ChallengeOptionDefinition { + id: string + label: string + icon: string + color: string + isCorrect: boolean + feedback: string +} + +export interface DailyChallengeDefinition { + id: string + question: string + coinReward: number + xpReward: number +} + +export const DAILY_CHALLENGE: DailyChallengeDefinition = { + id: 'daily-challenge', + question: + 'امروز ۲۰ هزار تومان داری کجا سرمایه‌گذاری می‌کنی که در آینده سود بیشتری داشته باشه؟', + coinReward: 50, + xpReward: 30, +} + +export const CHALLENGE_OPTIONS: ChallengeOptionDefinition[] = [ + { + id: 'gold', + label: 'طلا', + icon: '🥇', + color: '#f5b62d', + isCorrect: true, + feedback: 'عالی! طلا در بلندمدت می‌تونه ارزش پولت رو حفظ کنه.', + }, + { + id: 'dollar', + label: 'دلار', + icon: '💵', + color: '#20b94b', + isCorrect: false, + feedback: 'دلار گزینه‌ایه، ولی طلا برای حفظ ارزش در تورم بهتره.', + }, + { + id: 'bank', + label: 'بانک', + icon: '🏦', + color: '#1987d4', + isCorrect: false, + feedback: 'پس‌انداز بانکی امنه، ولی سودش معمولاً از تورم کمتره.', + }, + { + id: 'phone', + label: 'خرید گوشی', + icon: '📱', + color: '#7438c9', + isCorrect: false, + feedback: 'گوشی ارزش مالی نگه نمی‌داره. بهتره سرمایه‌گذاری کنی!', + }, +] + +export interface DailyChallengePublicOption { + id: string + label: string + icon: string + color: string +} + +export interface DailyChallengeResponse { + id: string + question: string + coinReward: number + xpReward: number + completed: boolean + options: DailyChallengePublicOption[] +} + +export type DailyChallengeCompleteResponse = import('../types/user.types').MeResponse & { + feedback: string + alreadyCompleted: boolean +} diff --git a/src/shared/catalog/default-user-state.ts b/src/shared/catalog/default-user-state.ts new file mode 100644 index 0000000..08b4341 --- /dev/null +++ b/src/shared/catalog/default-user-state.ts @@ -0,0 +1,52 @@ +import { createDefaultAchievements } from './achievements.constants' +import type { MapLevel, TimeRange, UserState } from '../types/user.types' + +export const DEMO_USER_ID = 'demo' + +export const MAP_LEVELS: MapLevel[] = [ + { id: 1, title: 'پول چیه؟', stars: 0, maxStars: 3, status: 'current', x: 24, y: 79 }, + { id: 2, title: 'پس‌انداز', stars: 0, maxStars: 3, status: 'locked', x: 59, y: 63 }, + { id: 3, title: 'نیاز و خواسته', stars: 0, maxStars: 3, status: 'locked', x: 32, y: 47 }, + { id: 4, title: 'طلا چیه؟', stars: 0, maxStars: 3, status: 'locked', x: 22, y: 36 }, + { id: 5, title: 'بازار طلا', stars: 0, maxStars: 3, status: 'locked', x: 42, y: 34 }, + { id: 6, title: 'تورم', stars: 0, maxStars: 3, status: 'locked', lessonRoute: '/lessons/inflation', x: 54, y: 66 }, + { id: 7, title: 'سرمایه‌گذاری', stars: 0, maxStars: 3, status: 'locked', x: 40, y: 61 }, + { id: 8, title: 'سبد دارایی', stars: 0, maxStars: 3, status: 'locked', x: 28, y: 77 }, + { id: 9, title: 'ریسک و پاداش', stars: 0, maxStars: 3, status: 'locked', x: 47, y: 63 }, + { id: 10, title: 'پس‌انداز هوشمند', stars: 0, maxStars: 3, status: 'locked', x: 33, y: 52 }, + { id: 11, title: 'اقتصاد خانواده', stars: 0, maxStars: 3, status: 'locked', x: 47, y: 41 }, + { id: 12, title: 'کارآفرینی', stars: 0, maxStars: 3, status: 'locked', x: 48, y: 30 }, + { id: 13, title: 'سرزمین طلا', stars: 0, maxStars: 3, status: 'locked', x: 54, y: 18 }, +] + +const INITIAL_TRANSACTIONS = [ + { id: '1', type: 'expense' as const, title: 'خرید طلا', amount: 1_600_000, unit: 'تومان' as const, date: '۱۴۰۴/۰۴/۱۲' }, + { id: '2', type: 'income' as const, title: 'ماموریت روزانه', amount: 50, unit: 'سکه' as const, date: '۱۴۰۴/۰۴/۱۱' }, + { id: '3', type: 'income' as const, title: 'پاداش بازی', amount: 20, unit: 'سکه' as const, date: '۱۴۰۴/۰۴/۱۰' }, +] + +export function createDefaultUserState(userId: string): UserState { + return { + id: userId, + name: 'علی', + level: 12, + xp: 850, + maxXp: 1200, + coins: 1350, + cash: 100_000, + goldGrams: 42.5, + missionProgress: 12, + missionTarget: 20, + completedChallenges: [], + completedLessons: [], + completedGames: [], + claimedRewards: [], + ownedItems: [], + achievements: createDefaultAchievements(), + mapLevels: MAP_LEVELS.map((l) => ({ ...l })), + totalStars: 0, + maxStars: 39, + marketSelectedRange: '1d' as TimeRange, + transactions: INITIAL_TRANSACTIONS.map((t) => ({ ...t })), + } +} diff --git a/src/shared/catalog/games.constants.ts b/src/shared/catalog/games.constants.ts new file mode 100644 index 0000000..cf295a9 --- /dev/null +++ b/src/shared/catalog/games.constants.ts @@ -0,0 +1,60 @@ +export interface GameDefinition { + id: string + title: string + description: string + icon: string + route?: string + color: string + rewardCoins?: number + rewardXp?: number +} + +export const GAMES: GameDefinition[] = [ + { + id: 'gold-guess', + title: 'حدس قیمت طلا', + description: 'قیمت فردا رو حدس بزن', + icon: '📈', + route: '/games/gold-guess', + color: '#7438c9', + rewardCoins: 20, + rewardXp: 50, + }, + { + id: 'inflation', + title: 'تورم رو شکست بده', + description: 'ارزش پولت رو حفظ کن', + icon: '👾', + route: '/lessons/inflation', + color: '#e73343', + rewardXp: 40, + }, + { + id: 'portfolio', + title: 'ساخت سبد سرمایه‌گذاری', + description: 'بهترین ترکیب رو انتخاب کن', + icon: '📊', + color: '#1987d4', + }, + { + id: 'smart-shop', + title: 'خرید هوشمند', + description: 'بهترین خرید رو انجام بده', + icon: '🛒', + color: '#20b94b', + }, + { + id: 'savings', + title: 'چالش پس‌انداز', + description: 'پس‌انداز کن و پیشرفت کن', + icon: '🐷', + color: '#f5b62d', + }, + { + id: 'investor', + title: 'شاگرد سرمایه‌گذار', + description: 'تصمیم بگیر و سود ببر', + icon: '🎓', + color: '#f07a2a', + }, +] diff --git a/src/shared/catalog/market.constants.ts b/src/shared/catalog/market.constants.ts new file mode 100644 index 0000000..66a87be --- /dev/null +++ b/src/shared/catalog/market.constants.ts @@ -0,0 +1,28 @@ +import type { TimeRange } from '../types/user.types' + +export const GOLD_PRICE = 1_830_000 +export const DAILY_CHANGE = 2.4 + +function generatePriceHistory(basePrice: number, points: number): number[] { + const history: number[] = [] + let price = basePrice * 0.92 + for (let i = 0; i < points; i++) { + const change = (Math.sin(i * 0.4) * 0.015 + Math.random() * 0.008) * price + price += change + history.push(Math.round(price)) + } + history[history.length - 1] = basePrice + return history +} + +export const PRICE_HISTORY: Record = { + '1d': generatePriceHistory(GOLD_PRICE, 24), + '7d': generatePriceHistory(GOLD_PRICE, 28), + '30d': generatePriceHistory(GOLD_PRICE, 30), + '90d': generatePriceHistory(GOLD_PRICE, 30), + '1y': generatePriceHistory(GOLD_PRICE, 52), +} + +export function formatFaDate(date = new Date()): string { + return date.toLocaleDateString('fa-IR') +} diff --git a/src/shared/catalog/store.constants.ts b/src/shared/catalog/store.constants.ts new file mode 100644 index 0000000..888d2d8 --- /dev/null +++ b/src/shared/catalog/store.constants.ts @@ -0,0 +1,18 @@ +export type StoreProductCategory = 'special' | 'visual' | 'boost' + +export interface StoreProduct { + id: string + title: string + price: number + category: StoreProductCategory + emoji: string +} + +export const STORE_PRODUCTS: StoreProduct[] = [ + { id: 'castle', title: 'قلعه طلایی', price: 5000, category: 'special', emoji: '🏰' }, + { id: 'suit', title: 'لباس ماجراجو', price: 2500, category: 'visual', emoji: '🧥' }, + { id: 'pet', title: 'حیوان همراه', price: 3000, category: 'special', emoji: '🦊' }, + { id: 'sea-bg', title: 'پس‌زمینه دریایی', price: 1500, category: 'visual', emoji: '🌊' }, + { id: 'crown', title: 'تاج طلایی', price: 4000, category: 'special', emoji: '👑' }, + { id: 'boost-xp', title: 'تقویت XP', price: 800, category: 'boost', emoji: '⚡' }, +] diff --git a/src/shared/economy/user-economy.helpers.ts b/src/shared/economy/user-economy.helpers.ts new file mode 100644 index 0000000..4163ae7 --- /dev/null +++ b/src/shared/economy/user-economy.helpers.ts @@ -0,0 +1,37 @@ +import { formatFaDate } from '../catalog/market.constants' +import type { Transaction, UserState } from '../types/user.types' + +export function addXp(state: UserState, amount: number): void { + state.xp += amount + while (state.xp >= state.maxXp) { + state.xp -= state.maxXp + state.level += 1 + state.maxXp = Math.round(state.maxXp * 1.15) + } +} + +export function addTransaction( + state: UserState, + tx: Omit & { date?: string }, +): void { + state.transactions.unshift({ + id: Date.now().toString() + Math.random().toString(36).slice(2, 6), + date: tx.date ?? formatFaDate(), + type: tx.type, + title: tx.title, + amount: tx.amount, + unit: tx.unit, + }) + if (state.transactions.length > 50) { + state.transactions = state.transactions.slice(0, 50) + } +} + +export function unlockAchievement(state: UserState, id: string): void { + const achievement = state.achievements.find((a) => a.id === id) + if (achievement) achievement.unlocked = true +} + +export function incrementMission(state: UserState): void { + state.missionProgress = Math.min(state.missionProgress + 1, state.missionTarget) +} diff --git a/src/shared/types/user.types.ts b/src/shared/types/user.types.ts new file mode 100644 index 0000000..925c6c0 --- /dev/null +++ b/src/shared/types/user.types.ts @@ -0,0 +1,98 @@ +export type TransactionType = 'income' | 'expense' +export type TransactionUnit = 'تومان' | 'سکه' | 'گرم' +export type TimeRange = '1d' | '7d' | '30d' | '90d' | '1y' +export type MapLevelStatus = 'completed' | 'current' | 'unlocked' | 'locked' + +export interface Transaction { + id: string + type: TransactionType + title: string + amount: number + unit: TransactionUnit + date: string +} + +export interface Achievement { + id: string + title: string + description: string + icon: string + unlocked: boolean +} + +export interface MapLevel { + id: number + title: string + stars: number + maxStars: number + status: MapLevelStatus + lessonRoute?: string + x: number + y: number +} + +export interface UserState { + id: string + name: string + level: number + xp: number + maxXp: number + coins: number + cash: number + goldGrams: number + missionProgress: number + missionTarget: number + completedChallenges: string[] + completedLessons: string[] + completedGames: string[] + claimedRewards: string[] + ownedItems: string[] + achievements: Achievement[] + mapLevels: MapLevel[] + totalStars: number + maxStars: number + marketSelectedRange: TimeRange + transactions: Transaction[] +} + +export interface MeResponse { + user: { + id: string + name: string + level: number + xp: number + maxXp: number + coins: number + missionProgress: number + missionTarget: number + } + wallet: { + cash: number + goldGrams: number + } + progress: { + completedChallenges: string[] + completedLessons: string[] + completedGames: string[] + claimedRewards: string[] + ownedItems: string[] + mapLevels: MapLevel[] + totalStars: number + maxStars: number + } + achievements: Achievement[] + transactions: Transaction[] + market: { + selectedRange: TimeRange + } +} + +export interface AuthResponse { + token: string + user: { + id: string + name: string + username: string + } + me: MeResponse +} diff --git a/src/store/dto/purchase-store-item.dto.ts b/src/store/dto/purchase-store-item.dto.ts new file mode 100644 index 0000000..9b2c2b6 --- /dev/null +++ b/src/store/dto/purchase-store-item.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from 'class-validator' + +export class PurchaseStoreItemDto { + @IsString() + @IsNotEmpty() + productId!: string +} diff --git a/src/store/store.controller.ts b/src/store/store.controller.ts new file mode 100644 index 0000000..364d1fe --- /dev/null +++ b/src/store/store.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Get, HttpCode, Inject, Post } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { PurchaseStoreItemDto } from './dto/purchase-store-item.dto' +import { StoreService } from './store.service' + +@Controller('api/store') +export class StoreController { + constructor(@Inject(StoreService) private readonly store: StoreService) {} + + @Get('products') + getProducts() { + return this.store.getProducts() + } + + @HttpCode(200) + @Post('purchases') + purchase( + @CurrentUser() userId: string, + @Body(strictBodyPipe(PurchaseStoreItemDto)) dto: PurchaseStoreItemDto, + ) { + return this.store.purchaseStoreItem(userId, dto.productId) + } +} diff --git a/src/store/store.module.ts b/src/store/store.module.ts new file mode 100644 index 0000000..07d9462 --- /dev/null +++ b/src/store/store.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { StoreController } from './store.controller' +import { StoreService } from './store.service' + +@Module({ + imports: [DatabaseModule, UsersModule], + controllers: [StoreController], + providers: [StoreService], +}) +export class StoreModule {} diff --git a/src/store/store.service.ts b/src/store/store.service.ts new file mode 100644 index 0000000..db19b2c --- /dev/null +++ b/src/store/store.service.ts @@ -0,0 +1,53 @@ +import { + BadRequestException, + ConflictException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { STORE_PRODUCTS, type StoreProduct } from '../shared/catalog/store.constants' +import { addTransaction } from '../shared/economy/user-economy.helpers' +import type { MeResponse } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' + +@Injectable() +export class StoreService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(UsersService) private readonly users: UsersService, + ) {} + + getProducts(): StoreProduct[] { + return STORE_PRODUCTS + } + + purchaseStoreItem(userId: string, productId: string): MeResponse { + const product = STORE_PRODUCTS.find((p) => p.id === productId) + if (!product) { + throw new NotFoundException('محصول پیدا نشد') + } + + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + + if (state.ownedItems.includes(productId)) { + throw new ConflictException('قبلاً خریدی!') + } + if (state.coins < product.price) { + throw new BadRequestException('سکه کافی نداری!') + } + + state.coins -= product.price + state.ownedItems.push(productId) + addTransaction(state, { + type: 'expense', + title: `خرید ${product.title}`, + amount: product.price, + unit: 'سکه', + }) + this.users.saveUserState(state) + return this.users.toMeResponse(state) + }) + } +} diff --git a/src/users/dto/update-password.dto.ts b/src/users/dto/update-password.dto.ts new file mode 100644 index 0000000..3d8bff9 --- /dev/null +++ b/src/users/dto/update-password.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty, IsString } from 'class-validator' + +export class UpdatePasswordDto { + @IsString() + @IsNotEmpty() + currentPassword!: string + + @IsString() + @IsNotEmpty() + newPassword!: string +} diff --git a/src/users/dto/update-profile.dto.ts b/src/users/dto/update-profile.dto.ts new file mode 100644 index 0000000..270a9f1 --- /dev/null +++ b/src/users/dto/update-profile.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from 'class-validator' + +export class UpdateProfileDto { + @IsString() + @IsNotEmpty() + name!: string +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts new file mode 100644 index 0000000..27b968e --- /dev/null +++ b/src/users/users.controller.ts @@ -0,0 +1,50 @@ +import { BadRequestException, Body, Controller, Get, HttpCode, Inject, Patch, forwardRef } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { AuthService } from '../auth/auth.service' +import { UsersService } from './users.service' +import { UpdateProfileDto } from './dto/update-profile.dto' +import { UpdatePasswordDto } from './dto/update-password.dto' + +@Controller('api') +export class UsersController { + constructor( + @Inject(UsersService) private readonly users: UsersService, + @Inject(forwardRef(() => AuthService)) + private readonly auth: AuthService, + ) {} + + @Get('me') + getMe(@CurrentUser() userId: string) { + return this.users.getMe(userId) + } + + @Patch('me/profile') + updateProfile( + @CurrentUser() userId: string, + @Body(strictBodyPipe(UpdateProfileDto)) dto: UpdateProfileDto, + ) { + const name = dto.name?.trim() + if (!name) { + throw new BadRequestException('نام نمایشی الزامی است') + } + return this.users.updateProfile(userId, name) + } + + @Patch('me/password') + updatePassword( + @CurrentUser() userId: string, + @Body(strictBodyPipe(UpdatePasswordDto)) dto: UpdatePasswordDto, + ) { + const currentPassword = dto.currentPassword ?? '' + const newPassword = dto.newPassword ?? '' + if (!currentPassword) { + throw new BadRequestException('رمز عبور فعلی الزامی است') + } + if (!newPassword) { + throw new BadRequestException('رمز عبور جدید الزامی است') + } + this.auth.updatePassword(userId, currentPassword, newPassword) + return { ok: true as const } + } +} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000..9b1c4ef --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,14 @@ +import { Module, forwardRef } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersService } from './users.service' +import { UsersController } from './users.controller' +import { GoldLandModule } from '../gold-land/gold-land.module' +import { AuthModule } from '../auth/auth.module' + +@Module({ + imports: [DatabaseModule, forwardRef(() => GoldLandModule), forwardRef(() => AuthModule)], + controllers: [UsersController], + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts new file mode 100644 index 0000000..19723d3 --- /dev/null +++ b/src/users/users.service.ts @@ -0,0 +1,107 @@ +import { + Inject, + Injectable, + InternalServerErrorException, + Logger, + forwardRef, +} from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { createDefaultUserState } from '../shared/catalog/default-user-state' +import type { MeResponse, UserState } from '../shared/types/user.types' +import { GoldLandProgressionService } from '../gold-land/services/progression.service' + +@Injectable() +export class UsersService { + private readonly logger = new Logger(UsersService.name) + + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(forwardRef(() => GoldLandProgressionService)) + private readonly goldLandProgression: GoldLandProgressionService, + ) {} + + ensureUser(userId: string): UserState { + return this.getUserState(userId) + } + + getUserState(userId: string): UserState { + const row = this.database + .getDb() + .prepare('SELECT state_json FROM users WHERE id = ?') + .get(userId) as { state_json: string } | undefined + + if (row) { + return this.parseStateJson(row.state_json, userId) + } + + const state = createDefaultUserState(userId) + this.database + .getDb() + .prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)') + .run(userId, JSON.stringify(state), new Date().toISOString()) + return state + } + + saveUserState(state: UserState): UserState { + this.database + .getDb() + .prepare('UPDATE users SET state_json = ?, updated_at = ? WHERE id = ?') + .run(JSON.stringify(state), new Date().toISOString(), state.id) + return state + } + + toMeResponse(state: UserState): MeResponse { + return { + user: { + id: state.id, + name: state.name, + level: state.level, + xp: state.xp, + maxXp: state.maxXp, + coins: state.coins, + missionProgress: state.missionProgress, + missionTarget: state.missionTarget, + }, + wallet: { + cash: state.cash, + goldGrams: state.goldGrams, + }, + progress: { + completedChallenges: state.completedChallenges, + completedLessons: state.completedLessons, + completedGames: state.completedGames, + claimedRewards: state.claimedRewards, + ownedItems: state.ownedItems, + mapLevels: state.mapLevels, + totalStars: state.totalStars, + maxStars: state.maxStars, + }, + achievements: state.achievements, + transactions: state.transactions, + market: { + selectedRange: state.marketSelectedRange, + }, + } + } + + getMe(userId: string): MeResponse { + const state = this.goldLandProgression.syncUserProgressionState(userId) + return this.toMeResponse(state) + } + + updateProfile(userId: string, name: string): MeResponse { + const state = this.getUserState(userId) + state.name = name.trim() + this.saveUserState(state) + return this.toMeResponse(state) + } + + private parseStateJson(raw: string, userId: string): UserState { + try { + return JSON.parse(raw) as UserState + } catch (error) { + this.logger.error(`Invalid state_json for user ${userId}`, error) + throw new InternalServerErrorException('داده کاربر نامعتبر است') + } + } +} diff --git a/src/wallet/dto/buy-gold.dto.ts b/src/wallet/dto/buy-gold.dto.ts new file mode 100644 index 0000000..6719f68 --- /dev/null +++ b/src/wallet/dto/buy-gold.dto.ts @@ -0,0 +1,6 @@ +import { IsNumber } from 'class-validator' + +export class BuyGoldDto { + @IsNumber({ allowNaN: false, allowInfinity: false }) + grams!: number +} diff --git a/src/wallet/dto/sell-gold.dto.ts b/src/wallet/dto/sell-gold.dto.ts new file mode 100644 index 0000000..169fef5 --- /dev/null +++ b/src/wallet/dto/sell-gold.dto.ts @@ -0,0 +1,6 @@ +import { IsNumber } from 'class-validator' + +export class SellGoldDto { + @IsNumber({ allowNaN: false, allowInfinity: false }) + grams!: number +} diff --git a/src/wallet/wallet-state.helpers.ts b/src/wallet/wallet-state.helpers.ts new file mode 100644 index 0000000..ec8085f --- /dev/null +++ b/src/wallet/wallet-state.helpers.ts @@ -0,0 +1,28 @@ +import { formatFaDate } from '../shared/catalog/market.constants' +import type { Transaction, UserState } from '../shared/types/user.types' + +export function addTransaction( + state: UserState, + tx: Omit & { date?: string }, +): void { + state.transactions.unshift({ + id: Date.now().toString() + Math.random().toString(36).slice(2, 6), + date: tx.date ?? formatFaDate(), + type: tx.type, + title: tx.title, + amount: tx.amount, + unit: tx.unit, + }) + if (state.transactions.length > 50) { + state.transactions = state.transactions.slice(0, 50) + } +} + +export function unlockAchievement(state: UserState, id: string): void { + const achievement = state.achievements.find((a) => a.id === id) + if (achievement) achievement.unlocked = true +} + +export function incrementMission(state: UserState): void { + state.missionProgress = Math.min(state.missionProgress + 1, state.missionTarget) +} diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts new file mode 100644 index 0000000..ad2a312 --- /dev/null +++ b/src/wallet/wallet.controller.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Get, HttpCode, Inject, Post } from '@nestjs/common' +import { CurrentUser } from '../common/decorators/current-user.decorator' +import { strictBodyPipe } from '../common/pipes/strict-body.pipe' +import { BuyGoldDto } from './dto/buy-gold.dto' +import { SellGoldDto } from './dto/sell-gold.dto' +import { WalletService } from './wallet.service' + +@Controller('api/wallet') +export class WalletController { + constructor(@Inject(WalletService) private readonly wallet: WalletService) {} + + @Get('transactions') + getTransactions(@CurrentUser() userId: string) { + return this.wallet.getTransactions(userId) + } + + @HttpCode(200) + @Post('gold/buy') + buyGold(@CurrentUser() userId: string, @Body(strictBodyPipe(BuyGoldDto)) dto: BuyGoldDto) { + return this.wallet.buyGold(userId, dto.grams) + } + + @HttpCode(200) + @Post('gold/sell') + sellGold(@CurrentUser() userId: string, @Body(strictBodyPipe(SellGoldDto)) dto: SellGoldDto) { + return this.wallet.sellGold(userId, dto.grams) + } +} diff --git a/src/wallet/wallet.module.ts b/src/wallet/wallet.module.ts new file mode 100644 index 0000000..5623fd9 --- /dev/null +++ b/src/wallet/wallet.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { DatabaseModule } from '../database/database.module' +import { UsersModule } from '../users/users.module' +import { WalletController } from './wallet.controller' +import { WalletService } from './wallet.service' + +@Module({ + imports: [DatabaseModule, UsersModule], + controllers: [WalletController], + providers: [WalletService], +}) +export class WalletModule {} diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts new file mode 100644 index 0000000..a0742b6 --- /dev/null +++ b/src/wallet/wallet.service.ts @@ -0,0 +1,73 @@ +import { BadRequestException, Inject, Injectable } from '@nestjs/common' +import { DatabaseService } from '../database/database.service' +import { GOLD_PRICE } from '../shared/catalog/market.constants' +import type { MeResponse, Transaction } from '../shared/types/user.types' +import { UsersService } from '../users/users.service' +import { addTransaction, incrementMission, unlockAchievement } from '../shared/economy/user-economy.helpers' + +@Injectable() +export class WalletService { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(UsersService) private readonly users: UsersService, + ) {} + + getTransactions(userId: string): { transactions: Transaction[] } { + const state = this.users.getUserState(userId) + return { transactions: state.transactions } + } + + buyGold(userId: string, grams: number): MeResponse { + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + const pricePerGram = GOLD_PRICE + const cost = grams * pricePerGram + + if (grams <= 0) { + throw new BadRequestException('مقدار نامعتبر است') + } + if (state.cash < cost) { + throw new BadRequestException('موجودی نقد کافی نیست') + } + + state.cash -= cost + state.goldGrams += grams + addTransaction(state, { + type: 'expense', + title: 'خرید طلا', + amount: cost, + unit: 'تومان', + }) + unlockAchievement(state, 'first-gold') + incrementMission(state) + this.users.saveUserState(state) + return this.users.toMeResponse(state) + }) + } + + sellGold(userId: string, grams: number): MeResponse { + return this.database.transaction(() => { + const state = this.users.getUserState(userId) + const pricePerGram = GOLD_PRICE + const value = grams * pricePerGram + + if (grams <= 0) { + throw new BadRequestException('مقدار نامعتبر است') + } + if (state.goldGrams < grams) { + throw new BadRequestException('طلای کافی نداری') + } + + state.goldGrams -= grams + state.cash += value + addTransaction(state, { + type: 'income', + title: 'فروش طلا', + amount: value, + unit: 'تومان', + }) + this.users.saveUserState(state) + return this.users.toMeResponse(state) + }) + } +} diff --git a/test/integration.test.ts b/test/integration.test.ts new file mode 100644 index 0000000..9a2cc73 --- /dev/null +++ b/test/integration.test.ts @@ -0,0 +1,2230 @@ +/** + * Phase B + Phase C integration tests — always uses a temporary SQLite file (never backend/data/app.db). + * Run: npm run test:integration + */ +import assert from 'node:assert/strict' +import { randomUUID } from 'node:crypto' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { ValidationPipe, BadRequestException } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { Test } from '@nestjs/testing' +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify' +import bcrypt from 'bcryptjs' +import { ApiExceptionFilter } from '../src/common/filters/api-exception.filter' +import { runSchemaMigrations } from '../src/database/migration.service' +import { DatabaseService } from '../src/database/database.service' + +const JWT_SECRET = 'gold-land-dev-secret-change-in-production' +const testDir = mkdtempSync(join(tmpdir(), 'gold-land-nest-test-')) +process.env.DB_PATH = join(testDir, 'test.db') +process.env.NODE_ENV = 'test' +process.env.PORT = '3999' +process.env.JWT_SECRET = JWT_SECRET + +async function createApp(): Promise { + const { AppModule } = await import('../src/app.module') + const moduleRef = await Test.createTestingModule({ + imports: [AppModule], + }).compile() + + const app = moduleRef.createNestApplication(new FastifyAdapter()) + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + }), + ) + app.useGlobalFilters(new ApiExceptionFilter()) + await app.init() + await app.getHttpAdapter().getInstance().ready() + return app +} + +function authHeader(token: string) { + return { authorization: `Bearer ${token}` } +} + +function parseJson(payload: string): T { + return JSON.parse(payload) as T +} + +function assertMeResponseShape(body: Record) { + assert.ok(body.user && typeof body.user === 'object') + assert.ok(body.wallet && typeof body.wallet === 'object') + assert.ok(body.progress && typeof body.progress === 'object') + assert.ok(Array.isArray(body.achievements)) + assert.ok(Array.isArray(body.transactions)) + assert.ok(body.market && typeof body.market === 'object') + const progress = body.progress as Record + assert.ok(Array.isArray(progress.mapLevels)) + assert.equal(typeof progress.totalStars, 'number') + assert.equal(typeof progress.maxStars, 'number') +} + +function assertFastifyDefaultAchievementSeed( + achievements: Array<{ id: string; unlocked: boolean }>, +) { + assert.equal(achievements.length, 4) + assert.deepEqual( + achievements.map((a) => ({ id: a.id, unlocked: a.unlocked })), + [ + { id: 'first-gold', unlocked: true }, + { id: 'saver', unlocked: true }, + { id: 'market', unlocked: false }, + { id: 'inflation', unlocked: false }, + ], + ) +} + +function testApiExceptionFilterShape() { + const filter = new ApiExceptionFilter() + let statusCode = 0 + let body: unknown + + const host = { + switchToHttp: () => ({ + getResponse: () => ({ + status(code: number) { + statusCode = code + return { + send(payload: unknown) { + body = payload + }, + } + }, + }), + }), + } + + filter.catch(new BadRequestException('فیلد نامعتبر'), host as never) + assert.equal(statusCode, 400) + assert.deepEqual(body, { error: 'فیلد نامعتبر' }) +} + +function runSchemaMigrationsTwice(dbService: DatabaseService) { + const db = dbService.getDb() + runSchemaMigrations(db) + runSchemaMigrations(db) + const check = dbService.verifyMigrationsApplied() + assert.equal(check.ok, true) +} + +async function runTests() { + testApiExceptionFilterShape() + + const app = await createApp() + const dbService = app.get(DatabaseService) + + try { + // 1–2. auth_accounts migration + idempotent re-run + const firstMigration = dbService.verifyMigrationsApplied() + assert.equal(firstMigration.ok, true, `missing tables: ${firstMigration.missing.join(', ')}`) + assert.ok(firstMigration.tables.includes('auth_accounts')) + runSchemaMigrationsTwice(dbService) + + const health = await app.inject({ method: 'GET', url: '/health' }) + assert.equal(health.statusCode, 200) + + // 13. request without token → 401 + const unauthorized = await app.inject({ method: 'GET', url: '/api/me' }) + assert.equal(unauthorized.statusCode, 401) + assert.deepEqual(parseJson(unauthorized.payload), { error: 'لطفاً وارد شوید' }) + + // 10. demo / 123456 on seed + const demoLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: 'demo', password: '123456' }, + }) + assert.equal(demoLogin.statusCode, 200) + const demoBody = parseJson<{ + token: string + user: { id: string; username: string; name: string } + me: Record + }>(demoLogin.payload) + assert.equal(demoBody.user.id, 'demo') + assert.equal(demoBody.user.username, 'demo') + assert.ok(demoBody.token) + assertMeResponseShape(demoBody.me) + + // 11. JWT payload contains userId + const jwtService = app.get(JwtService) + const decoded = jwtService.decode(demoBody.token) as { userId?: string } + assert.equal(decoded.userId, 'demo') + + // 12. token signed with legacy-compatible secret accepted by Nest + const legacyToken = jwtService.sign({ userId: 'demo' }) + const meWithLegacyToken = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(legacyToken), + }) + assert.equal(meWithLegacyToken.statusCode, 200) + assertMeResponseShape(parseJson(meWithLegacyToken.payload)) + + // 14. malformed token → 401 + const badToken = await app.inject({ + method: 'GET', + url: '/api/me', + headers: { authorization: 'Bearer not-a-valid-jwt' }, + }) + assert.equal(badToken.statusCode, 401) + assert.deepEqual(parseJson(badToken.payload), { + error: 'نشست نامعتبر است. دوباره وارد شوید', + }) + + // 3. register success + const username = `user_${randomUUID().slice(0, 8)}` + const registerRes = await app.inject({ + method: 'POST', + url: '/api/auth/register', + payload: { username, password: 'secret12', name: 'کاربر تست' }, + }) + assert.equal(registerRes.statusCode, 200) + const registerBody = parseJson<{ + token: string + user: { id: string; username: string; name: string } + me: Record + }>(registerRes.payload) + assert.equal(registerBody.user.username, username.toLowerCase()) + assert.equal(registerBody.user.name, 'کاربر تست') + assert.ok(registerBody.token) + assertMeResponseShape(registerBody.me) + assert.equal(JSON.stringify(registerRes.payload).includes('password_hash'), false) + assert.equal(JSON.stringify(registerRes.payload).includes('passwordHash'), false) + + // 6. password hash not leaked in API + const hashRow = dbService + .getDb() + .prepare('SELECT password_hash FROM auth_accounts WHERE username = ?') + .get(username.toLowerCase()) as { password_hash: string } + assert.ok(bcrypt.compareSync('secret12', hashRow.password_hash)) + + // 4. duplicate username → 409 + const duplicate = await app.inject({ + method: 'POST', + url: '/api/auth/register', + payload: { username, password: 'secret12' }, + }) + assert.equal(duplicate.statusCode, 409) + assert.deepEqual(parseJson(duplicate.payload), { error: 'این نام کاربری قبلاً ثبت شده' }) + + // 5 + 22. unknown/extra fields rejected on register + const extraField = await app.inject({ + method: 'POST', + url: '/api/auth/register', + headers: { 'content-type': 'application/json' }, + payload: JSON.stringify({ username: 'extra_user', password: 'secret12', hacker: true }), + }) + assert.equal(extraField.statusCode, 400) + + // 7. login success + const loginRes = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username, password: 'secret12' }, + }) + assert.equal(loginRes.statusCode, 200) + const loginBody = parseJson<{ token: string; user: { username: string } }>(loginRes.payload) + assert.equal(loginBody.user.username, username.toLowerCase()) + + // 8–9. login failures + const badUser = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: 'nobody_here', password: 'secret12' }, + }) + assert.equal(badUser.statusCode, 401) + assert.deepEqual(parseJson(badUser.payload), { error: 'نام کاربری یا رمز عبور اشتباه است' }) + + const badPass = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username, password: 'wrongpass' }, + }) + assert.equal(badPass.statusCode, 401) + assert.deepEqual(parseJson(badPass.payload), { error: 'نام کاربری یا رمز عبور اشتباه است' }) + + // Legacy hash compatibility — pre-existing bcrypt hash verifies + const legacyUser = `legacy_${randomUUID().slice(0, 8)}` + const legacyUserId = randomUUID() + const legacyHash = bcrypt.hashSync('legacyPass1', 10) + const now = new Date().toISOString() + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + legacyUserId, + JSON.stringify({ id: legacyUserId, name: 'Legacy', level: 1, xp: 0, maxXp: 100, coins: 50, cash: 1000, goldGrams: 0, missionProgress: 0, missionTarget: 3, completedChallenges: [], completedLessons: [], completedGames: [], claimedRewards: [], ownedItems: [], achievements: [], mapLevels: [], totalStars: 0, maxStars: 39, marketSelectedRange: '7d', transactions: [] }), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), legacyUser, legacyHash, legacyUserId, now) + + const legacyLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: legacyUser, password: 'legacyPass1' }, + }) + assert.equal(legacyLogin.statusCode, 200) + + const userToken = loginBody.token + + const extraProfileField = await app.inject({ + method: 'PATCH', + url: '/api/me/profile', + headers: { ...authHeader(userToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ name: 'نام', extra: true }), + }) + assert.equal(extraProfileField.statusCode, 400) + + // 15. GET /api/me shape + const meRes = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(userToken), + }) + assert.equal(meRes.statusCode, 200) + const meBody = parseJson>(meRes.payload) + assertMeResponseShape(meBody) + + // 16. GET /api/me for JWT user not yet in DB — ensureUser creates user + const freshUserId = randomUUID() + const freshToken = jwtService.sign({ userId: freshUserId }) + const freshMe = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(freshToken), + }) + assert.equal(freshMe.statusCode, 200) + const freshMeBody = parseJson<{ user: { id: string } }>(freshMe.payload) + assert.equal(freshMeBody.user.id, freshUserId) + + // corrupted state_json → controlled error + dbService.getDb().prepare('UPDATE users SET state_json = ? WHERE id = ?').run('{bad json', freshUserId) + const corruptMe = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(freshToken), + }) + assert.equal(corruptMe.statusCode, 401) + assert.deepEqual(parseJson(corruptMe.payload), { + error: 'نشست نامعتبر است. دوباره وارد شوید', + }) + + // 17–18. profile update persists without wiping other state fields + const beforeProfile = parseJson<{ user: { name: string }; wallet: { cash: number } }>( + ( + await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(userToken), + }) + ).payload, + ) + const originalCash = beforeProfile.wallet.cash + + const profileRes = await app.inject({ + method: 'PATCH', + url: '/api/me/profile', + headers: authHeader(userToken), + payload: { name: 'نام جدید' }, + }) + assert.equal(profileRes.statusCode, 200) + const profileBody = parseJson<{ user: { name: string }; wallet: { cash: number } }>( + profileRes.payload, + ) + assert.equal(profileBody.user.name, 'نام جدید') + assert.equal(profileBody.wallet.cash, originalCash) + + const profileAgain = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(userToken), + }) + assert.equal(parseJson<{ user: { name: string } }>(profileAgain.payload).user.name, 'نام جدید') + + const invalidProfile = await app.inject({ + method: 'PATCH', + url: '/api/me/profile', + headers: authHeader(userToken), + payload: { name: ' ' }, + }) + assert.equal(invalidProfile.statusCode, 400) + assert.deepEqual(parseJson(invalidProfile.payload), { error: 'نام نمایشی الزامی است' }) + + // 19–21. password change flow + const passwordRes = await app.inject({ + method: 'PATCH', + url: '/api/me/password', + headers: authHeader(userToken), + payload: { currentPassword: 'secret12', newPassword: 'newpass9' }, + }) + assert.equal(passwordRes.statusCode, 200) + assert.deepEqual(parseJson(passwordRes.payload), { ok: true }) + + const oldPassLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username, password: 'secret12' }, + }) + assert.equal(oldPassLogin.statusCode, 401) + + const newPassLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username, password: 'newpass9' }, + }) + assert.equal(newPassLogin.statusCode, 200) + + const wrongCurrent = await app.inject({ + method: 'PATCH', + url: '/api/me/password', + headers: authHeader(parseJson<{ token: string }>(newPassLogin.payload).token), + payload: { currentPassword: 'wrong', newPassword: 'another1' }, + }) + assert.equal(wrongCurrent.statusCode, 401) + assert.deepEqual(parseJson(wrongCurrent.payload), { error: 'رمز عبور فعلی اشتباه است' }) + + const shortNew = await app.inject({ + method: 'PATCH', + url: '/api/me/password', + headers: authHeader(parseJson<{ token: string }>(newPassLogin.payload).token), + payload: { currentPassword: 'newpass9', newPassword: '123' }, + }) + assert.equal(shortNew.statusCode, 400) + assert.deepEqual(parseJson(shortNew.payload), { error: 'رمز عبور جدید باید حداقل ۶ کاراکتر باشد' }) + + // password change for user without auth account → 404 + const orphanId = randomUUID() + const orphanToken = jwtService.sign({ userId: orphanId }) + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + orphanId, + JSON.stringify({ id: orphanId, name: 'Orphan', level: 1, xp: 0, maxXp: 100, coins: 0, cash: 0, goldGrams: 0, missionProgress: 0, missionTarget: 3, completedChallenges: [], completedLessons: [], completedGames: [], claimedRewards: [], ownedItems: [], achievements: [], mapLevels: [], totalStars: 0, maxStars: 39, marketSelectedRange: '7d', transactions: [] }), + now, + ) + const orphanPass = await app.inject({ + method: 'PATCH', + url: '/api/me/password', + headers: authHeader(orphanToken), + payload: { currentPassword: 'x', newPassword: 'yyyyyy' }, + }) + assert.equal(orphanPass.statusCode, 404) + assert.deepEqual(parseJson(orphanPass.payload), { error: 'حساب کاربری پیدا نشد' }) + + // --- Phase D: Gold Land integration --- + const { getGradableQuestions } = await import('../src/gold-land/lessons/lesson.grader') + const { inflationLesson } = await import('../src/gold-land/lessons/inflation.lesson') + const { GOLD_LAND_LEVELS } = await import('../src/gold-land/levels/levels') + + const glToken = parseJson<{ token: string }>(newPassLogin.payload).token + + const levelsMeta = await app.inject({ + method: 'GET', + url: '/api/gold-land/levels', + headers: authHeader(glToken), + }) + assert.equal(levelsMeta.statusCode, 200) + const levelsBody = parseJson<{ levels: Array<{ id: number; isActive: boolean }> }>(levelsMeta.payload) + assert.equal(levelsBody.levels.length, GOLD_LAND_LEVELS.length) + assert.equal(levelsBody.levels.filter((l) => l.isActive).length, 6) + + const noAuthProgress = await app.inject({ method: 'GET', url: '/api/gold-land/progress' }) + assert.equal(noAuthProgress.statusCode, 401) + + const progressRes = await app.inject({ + method: 'GET', + url: '/api/gold-land/progress', + headers: authHeader(glToken), + }) + assert.equal(progressRes.statusCode, 200) + const progressBody = parseJson<{ + levels: Array<{ id: number; status: string }> + totalStars: number + maxStars: number + }>(progressRes.payload) + assert.equal(progressBody.maxStars, 39) + assert.equal(progressBody.levels.find((l) => l.id === 1)?.status !== 'locked', true) + assert.equal(progressBody.levels.find((l) => l.id === 6)?.status, 'locked') + + const lockedStart = await app.inject({ + method: 'POST', + url: '/api/gold-land/levels/6/start', + headers: authHeader(glToken), + }) + assert.equal(lockedStart.statusCode, 403) + + const glUserId = dbService + .getDb() + .prepare('SELECT user_id FROM auth_accounts WHERE username = ?') + .get(username.toLowerCase()) as { user_id: string } + for (let levelId = 1; levelId <= 5; levelId++) { + dbService.getDb().prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, started_at, completed_at, updated_at + ) VALUES (?, ?, 65, 1, 1, ?, ?, ?) + ON CONFLICT(user_id, level_id) DO UPDATE SET stars = 1, best_score = 65, completed_at = excluded.completed_at + `).run(glUserId.user_id, levelId, now, now, now) + } + + const levelDetail = await app.inject({ + method: 'GET', + url: '/api/gold-land/levels/6', + headers: authHeader(glToken), + }) + assert.equal(levelDetail.statusCode, 200) + assert.ok(!levelDetail.payload.includes('correctAnswerId')) + assert.ok(!levelDetail.payload.includes('correctOrder')) + + const startRes = await app.inject({ + method: 'POST', + url: '/api/gold-land/levels/6/start', + headers: authHeader(glToken), + }) + assert.equal(startRes.statusCode, 200) + assert.deepEqual(parseJson(startRes.payload), { levelId: 6, status: 'in_progress' }) + + const inflationAnswers = getGradableQuestions(inflationLesson).map(({ questionId, step }) => { + if (step.type === 'sort') { + return { questionId, orderedItemIds: [...step.correctOrder] } + } + return { questionId, answerId: step.correctAnswerId } + }) + + const completeRes = await app.inject({ + method: 'POST', + url: '/api/gold-land/levels/6/complete', + headers: { ...authHeader(glToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ answers: inflationAnswers, durationMs: 5000 }), + }) + assert.equal(completeRes.statusCode, 200) + const completeBody = parseJson<{ + levelId: number + stars: number + score: number + reward: { xp: number; coins: number } + me: { progress: { mapLevels: Array<{ id: number; stars: number }> } } + }>(completeRes.payload) + assert.equal(completeBody.levelId, 6) + assert.equal(completeBody.stars, 3) + assert.equal(completeBody.score, 100) + assert.ok(completeBody.reward.xp > 0) + assert.equal(completeBody.me.progress.mapLevels.find((l) => l.id === 6)?.stars, 3) + + const meAfter = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(glToken), + }) + assert.equal(meAfter.statusCode, 200) + const glMeBody = parseJson<{ progress: { mapLevels: Array<{ id: number; stars: number }> } }>( + meAfter.payload, + ) + assert.equal(glMeBody.progress.mapLevels.find((l) => l.id === 6)?.stars, 3) + + const duplicateComplete = await app.inject({ + method: 'POST', + url: '/api/gold-land/levels/6/complete', + headers: { ...authHeader(glToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ answers: inflationAnswers }), + }) + assert.equal(duplicateComplete.statusCode, 200) + assert.equal(parseJson<{ reward: { xp: number } }>(duplicateComplete.payload).reward.xp, 0) + + const extraFieldComplete = await app.inject({ + method: 'POST', + url: '/api/gold-land/levels/6/complete', + headers: { ...authHeader(glToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ answers: inflationAnswers, score: 100 }), + }) + assert.equal(extraFieldComplete.statusCode, 400) + + // --- Phase E: Wallet + Market integration --- + const { GOLD_PRICE } = await import('../src/shared/catalog/market.constants') + + const walletUser = `wallet_${randomUUID().slice(0, 8)}` + const walletUserId = randomUUID() + const walletHash = bcrypt.hashSync('walletPass1', 10) + const walletState = { + id: walletUserId, + name: 'Wallet Test', + level: 5, + xp: 100, + maxXp: 200, + coins: 10_000, + cash: 100_000, + goldGrams: 42.5, + missionProgress: 3, + missionTarget: 20, + completedChallenges: ['ch-1'], + completedLessons: ['lesson-1'], + completedGames: ['game-1'], + claimedRewards: ['reward-1'], + ownedItems: ['item-1'], + achievements: [ + { id: 'first-gold', title: 'اولین خرید طلا', description: 'd', icon: '🥇', unlocked: false }, + ], + mapLevels: [{ id: 1, title: 'تست', stars: 2, maxStars: 3, status: 'completed', x: 10, y: 20 }], + totalStars: 2, + maxStars: 39, + marketSelectedRange: '1d', + transactions: [ + { id: 'seed-1', type: 'expense', title: 'خرید طلا', amount: 1000, unit: 'تومان', date: '۱۴۰۴/۰۱/۰۱' }, + ], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + walletUserId, + JSON.stringify(walletState), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), walletUser, walletHash, walletUserId, now) + + const walletLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: walletUser, password: 'walletPass1' }, + }) + assert.equal(walletLogin.statusCode, 200) + const walletToken = parseJson<{ token: string }>(walletLogin.payload).token + + const walletEndpoints = [ + { method: 'GET' as const, url: '/api/wallet/transactions' }, + { method: 'POST' as const, url: '/api/wallet/gold/buy', payload: { grams: 0.01 } }, + { method: 'POST' as const, url: '/api/wallet/gold/sell', payload: { grams: 0.01 } }, + { method: 'GET' as const, url: '/api/market/gold?range=1d' }, + { method: 'PATCH' as const, url: '/api/market/preferences', payload: { range: '7d' } }, + ] + for (const ep of walletEndpoints) { + const unauth = await app.inject({ + method: ep.method, + url: ep.url, + payload: ep.payload, + }) + assert.equal(unauth.statusCode, 401, `expected 401 for ${ep.method} ${ep.url}`) + } + + const txRes = await app.inject({ + method: 'GET', + url: '/api/wallet/transactions', + headers: authHeader(walletToken), + }) + assert.equal(txRes.statusCode, 200) + const txBody = parseJson<{ transactions: Array<{ id: string; type: string; title: string }> }>( + txRes.payload, + ) + assert.ok(Array.isArray(txBody.transactions)) + assert.equal(txBody.transactions.length, 1) + assert.equal(txBody.transactions[0].id, 'seed-1') + + const marketRes = await app.inject({ + method: 'GET', + url: '/api/market/gold?range=7d', + headers: authHeader(walletToken), + }) + assert.equal(marketRes.statusCode, 200) + const marketBody = parseJson<{ + currentPrice: number + dailyChangePercent: number + range: string + history: number[] + updatedAt: string + }>(marketRes.payload) + assert.equal(marketBody.currentPrice, GOLD_PRICE) + assert.equal(marketBody.dailyChangePercent, 2.4) + assert.equal(marketBody.range, '7d') + assert.ok(Array.isArray(marketBody.history)) + assert.equal(marketBody.history.length, 28) + assert.equal(marketBody.history[marketBody.history.length - 1], GOLD_PRICE) + assert.ok(marketBody.updatedAt) + + const buyRes = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: { ...authHeader(walletToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ grams: 0.05 }), + }) + assert.equal(buyRes.statusCode, 200) + const buyBody = parseJson<{ + wallet: { cash: number; goldGrams: number } + transactions: Array<{ type: string; title: string; amount: number; unit: string }> + progress: { completedChallenges: string[]; mapLevels: Array<{ id: number; stars: number }> } + achievements: Array<{ id: string; unlocked: boolean }> + }>(buyRes.payload) + assert.equal(buyBody.wallet.cash, 100_000 - 0.05 * GOLD_PRICE) + assert.equal(buyBody.wallet.goldGrams, 42.55) + assert.ok(buyBody.wallet.cash >= 0) + assert.ok(buyBody.wallet.goldGrams >= 0) + assert.equal(buyBody.transactions[0].type, 'expense') + assert.equal(buyBody.transactions[0].title, 'خرید طلا') + assert.equal(buyBody.transactions[0].amount, 0.05 * GOLD_PRICE) + assert.equal(buyBody.transactions[0].unit, 'تومان') + assert.deepEqual(buyBody.progress.completedChallenges, ['ch-1']) + assert.equal(buyBody.progress.mapLevels[0].stars, 2) + assert.equal(buyBody.achievements.find((a) => a.id === 'first-gold')?.unlocked, true) + + const meAfterBuy = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(walletToken), + }) + assert.equal(meAfterBuy.statusCode, 200) + const meBuyBody = parseJson<{ wallet: { cash: number; goldGrams: number } }>(meAfterBuy.payload) + assert.equal(meBuyBody.wallet.cash, buyBody.wallet.cash) + assert.equal(meBuyBody.wallet.goldGrams, buyBody.wallet.goldGrams) + + const sellRes = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/sell', + headers: { ...authHeader(walletToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ grams: 0.001 }), + }) + assert.equal(sellRes.statusCode, 200) + const sellBody = parseJson<{ + wallet: { cash: number; goldGrams: number } + transactions: Array<{ type: string; title: string; amount: number }> + }>(sellRes.payload) + assert.equal(sellBody.wallet.goldGrams, 42.549) + assert.equal(sellBody.wallet.cash, buyBody.wallet.cash + 0.001 * GOLD_PRICE) + assert.equal(sellBody.transactions[0].type, 'income') + assert.equal(sellBody.transactions[0].title, 'فروش طلا') + assert.equal(sellBody.transactions[0].amount, 0.001 * GOLD_PRICE) + + const meAfterSell = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(walletToken), + }) + assert.equal(meAfterSell.statusCode, 200) + const meSellBody = parseJson<{ wallet: { cash: number; goldGrams: number } }>(meAfterSell.payload) + assert.equal(meSellBody.wallet.cash, sellBody.wallet.cash) + assert.equal(meSellBody.wallet.goldGrams, sellBody.wallet.goldGrams) + + const zeroBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: 0 }, + }) + assert.equal(zeroBuy.statusCode, 400) + assert.deepEqual(parseJson(zeroBuy.payload), { error: 'مقدار نامعتبر است' }) + + const negativeBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: -1 }, + }) + assert.equal(negativeBuy.statusCode, 400) + assert.deepEqual(parseJson(negativeBuy.payload), { error: 'مقدار نامعتبر است' }) + + const nanBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: Number.NaN }, + }) + assert.equal(nanBuy.statusCode, 400) + + const infinityBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: Number.POSITIVE_INFINITY }, + }) + assert.equal(infinityBuy.statusCode, 400) + + const extraFieldBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: { ...authHeader(walletToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ grams: 0.01, goldPrice: 1 }), + }) + assert.equal(extraFieldBuy.statusCode, 400) + + const stateBeforeFail = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(walletUserId) as { + state_json: string + } + ).state_json + + const insufficientCash = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: 1000 }, + }) + assert.equal(insufficientCash.statusCode, 400) + assert.deepEqual(parseJson(insufficientCash.payload), { error: 'موجودی نقد کافی نیست' }) + + const stateAfterFailBuy = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(walletUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterFailBuy, stateBeforeFail, 'failed buy must rollback state') + + const insufficientGold = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/sell', + headers: authHeader(walletToken), + payload: { grams: 9999 }, + }) + assert.equal(insufficientGold.statusCode, 400) + assert.deepEqual(parseJson(insufficientGold.payload), { error: 'طلای کافی نداری' }) + + const stateAfterFailSell = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(walletUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterFailSell, stateBeforeFail, 'failed sell must rollback state') + + const prefsRes = await app.inject({ + method: 'PATCH', + url: '/api/market/preferences', + headers: authHeader(walletToken), + payload: { range: '30d' }, + }) + assert.equal(prefsRes.statusCode, 200) + const prefsBody = parseJson<{ market: { selectedRange: string }; progress: { completedLessons: string[] } }>( + prefsRes.payload, + ) + assert.equal(prefsBody.market.selectedRange, '30d') + assert.deepEqual(prefsBody.progress.completedLessons, ['lesson-1']) + + const extraFieldPrefs = await app.inject({ + method: 'PATCH', + url: '/api/market/preferences', + headers: { ...authHeader(walletToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ range: '7d', extra: true }), + }) + assert.equal(extraFieldPrefs.statusCode, 400) + + const invalidRangePrefs = await app.inject({ + method: 'PATCH', + url: '/api/market/preferences', + headers: authHeader(walletToken), + payload: { range: 'invalid' }, + }) + assert.equal(invalidRangePrefs.statusCode, 400) + + const invalidMarketRange = await app.inject({ + method: 'GET', + url: '/api/market/gold?range=invalid', + headers: authHeader(walletToken), + }) + assert.equal(invalidMarketRange.statusCode, 200) + const invalidRangeBody = parseJson<{ range: string; history: number[] }>(invalidMarketRange.payload) + assert.equal(invalidRangeBody.range, 'invalid') + assert.equal(invalidRangeBody.history.length, 24) + + // precision: third decimal trade + const precisionBuy = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: authHeader(walletToken), + payload: { grams: 0.005 }, + }) + assert.equal(precisionBuy.statusCode, 200) + const precisionBody = parseJson<{ wallet: { cash: number; goldGrams: number } }>(precisionBuy.payload) + assert.equal(precisionBody.wallet.goldGrams, sellBody.wallet.goldGrams + 0.005) + assert.equal(precisionBody.wallet.cash, sellBody.wallet.cash - 0.005 * GOLD_PRICE) + assert.ok(precisionBody.wallet.cash >= 0) + assert.ok(precisionBody.wallet.goldGrams >= 0) + + // transaction order: newest first + const txOrder = parseJson<{ transactions: Array<{ title: string }> }>(precisionBuy.payload) + assert.equal(txOrder.transactions[0].title, 'خرید طلا') + + // --- Phase F1: Store integration --- + const { STORE_PRODUCTS } = await import('../src/shared/catalog/store.constants') + + const storeUser = `store_${randomUUID().slice(0, 8)}` + const storeUserId = randomUUID() + const storeHash = bcrypt.hashSync('storePass1', 10) + const storeState = { + id: storeUserId, + name: 'Store Test', + level: 5, + xp: 100, + maxXp: 200, + coins: 10_000, + cash: 50_000, + goldGrams: 10, + missionProgress: 7, + missionTarget: 20, + completedChallenges: ['ch-a'], + completedLessons: ['lesson-a'], + completedGames: ['game-a'], + claimedRewards: ['reward-a'], + ownedItems: [] as string[], + achievements: [ + { id: 'first-gold', title: 'اولین خرید طلا', description: 'd', icon: '🥇', unlocked: true }, + { id: 'market', title: 'شاگرد بازار', description: 'd', icon: '📊', unlocked: false }, + ], + mapLevels: [{ id: 1, title: 'تست', stars: 1, maxStars: 3, status: 'completed', x: 10, y: 20 }], + totalStars: 1, + maxStars: 39, + marketSelectedRange: '7d', + transactions: [ + { id: 'seed-tx', type: 'income', title: 'seed', amount: 1, unit: 'سکه', date: '۱۴۰۴/۰۱/۰۱' }, + ], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + storeUserId, + JSON.stringify(storeState), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), storeUser, storeHash, storeUserId, now) + + const storeLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: storeUser, password: 'storePass1' }, + }) + assert.equal(storeLogin.statusCode, 200) + const storeToken = parseJson<{ token: string }>(storeLogin.payload).token + + const storeUnauthGet = await app.inject({ method: 'GET', url: '/api/store/products' }) + assert.equal(storeUnauthGet.statusCode, 401) + const storeUnauthPost = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + payload: { productId: 'suit' }, + }) + assert.equal(storeUnauthPost.statusCode, 401) + + const productsRes = await app.inject({ + method: 'GET', + url: '/api/store/products', + headers: authHeader(storeToken), + }) + assert.equal(productsRes.statusCode, 200) + const productsBody = parseJson< + Array<{ id: string; title: string; price: number; category: string; emoji: string }> + >(productsRes.payload) + assert.equal(productsBody.length, 6) + assert.deepEqual( + productsBody.map((p) => p.id), + STORE_PRODUCTS.map((p) => p.id), + ) + assert.equal(productsBody[0].title, 'قلعه طلایی') + assert.equal(productsBody[0].price, 5000) + assert.equal(productsBody[0].category, 'special') + assert.equal(productsBody[0].emoji, '🏰') + + const stateBeforeAnyPurchase = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(storeUserId) as { + state_json: string + } + ).state_json + const lowCoinsState = JSON.parse(stateBeforeAnyPurchase) as typeof storeState + lowCoinsState.coins = 100 + const lowCoinsJson = JSON.stringify(lowCoinsState) + dbService.getDb().prepare('UPDATE users SET state_json = ? WHERE id = ?').run(lowCoinsJson, storeUserId) + + const insufficientCoins = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: 'castle' }, + }) + assert.equal(insufficientCoins.statusCode, 400) + assert.deepEqual(parseJson(insufficientCoins.payload), { error: 'سکه کافی نداری!' }) + const stateAfterInsufficient = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(storeUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterInsufficient, lowCoinsJson) + + lowCoinsState.coins = 10_000 + dbService.getDb().prepare('UPDATE users SET state_json = ? WHERE id = ?').run( + JSON.stringify(lowCoinsState), + storeUserId, + ) + + const purchaseRes = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: 'suit' }, + }) + assert.equal(purchaseRes.statusCode, 200) + const purchaseBody = parseJson<{ + user: { coins: number; missionProgress: number; level: number; xp: number } + progress: { ownedItems: string[]; completedGames: string[] } + transactions: Array<{ type: string; title: string; amount: number; unit: string }> + achievements: Array<{ id: string; unlocked: boolean }> + }>(purchaseRes.payload) + assert.equal(purchaseBody.user.coins, 10_000 - 2500) + assert.deepEqual(purchaseBody.progress.ownedItems, ['suit']) + assert.equal(purchaseBody.transactions[0].type, 'expense') + assert.equal(purchaseBody.transactions[0].title, 'خرید لباس ماجراجو') + assert.equal(purchaseBody.transactions[0].amount, 2500) + assert.equal(purchaseBody.transactions[0].unit, 'سکه') + assert.equal(purchaseBody.user.missionProgress, 7) + assert.equal(purchaseBody.user.level, 5) + assert.equal(purchaseBody.user.xp, 100) + assert.equal(purchaseBody.achievements.find((a) => a.id === 'market')?.unlocked, false) + assert.deepEqual(purchaseBody.progress.completedGames, ['game-a']) + + const meAfterPurchase = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(storeToken), + }) + assert.equal(meAfterPurchase.statusCode, 200) + const mePurchaseBody = parseJson<{ + user: { coins: number } + progress: { ownedItems: string[] } + }>(meAfterPurchase.payload) + assert.equal(mePurchaseBody.user.coins, 7500) + assert.deepEqual(mePurchaseBody.progress.ownedItems, ['suit']) + + const stateBeforeDuplicate = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(storeUserId) as { + state_json: string + } + ).state_json + + const duplicatePurchase = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: 'suit' }, + }) + assert.equal(duplicatePurchase.statusCode, 409) + assert.deepEqual(parseJson(duplicatePurchase.payload), { error: 'قبلاً خریدی!' }) + const stateAfterDuplicate = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(storeUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterDuplicate, stateBeforeDuplicate) + + const invalidProduct = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: 'not-a-product' }, + }) + assert.equal(invalidProduct.statusCode, 404) + assert.deepEqual(parseJson(invalidProduct.payload), { error: 'محصول پیدا نشد' }) + + const emptyProductId = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: '' }, + }) + assert.equal(emptyProductId.statusCode, 400) + + const emptyBody = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: { ...authHeader(storeToken), 'content-type': 'application/json' }, + payload: JSON.stringify({}), + }) + assert.equal(emptyBody.statusCode, 400) + + const extraFieldPurchase = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: { ...authHeader(storeToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ productId: 'pet', price: 1 }), + }) + assert.equal(extraFieldPurchase.statusCode, 400) + + const clientPriceField = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: { ...authHeader(storeToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ productId: 'pet', coins: 99999 }), + }) + assert.equal(clientPriceField.statusCode, 400) + + const boostPurchase = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: authHeader(storeToken), + payload: { productId: 'boost-xp' }, + }) + assert.equal(boostPurchase.statusCode, 200) + const boostBody = parseJson<{ + user: { coins: number; level: number; xp: number; maxXp: number } + progress: { ownedItems: string[] } + transactions: Array<{ title: string; amount: number }> + }>(boostPurchase.payload) + assert.equal(boostBody.user.coins, 7500 - 800) + assert.ok(boostBody.progress.ownedItems.includes('boost-xp')) + assert.equal(boostBody.user.level, 5) + assert.equal(boostBody.user.xp, 100) + assert.equal(boostBody.transactions[0].title, 'خرید تقویت XP') + assert.equal(boostBody.transactions[0].amount, 800) + + // --- Phase F2: Games integration --- + const { GAMES } = await import('../src/shared/catalog/games.constants') + + const gamesUser = `games_${randomUUID().slice(0, 8)}` + const gamesUserId = randomUUID() + const gamesHash = bcrypt.hashSync('gamesPass1', 10) + const gamesState = { + id: gamesUserId, + name: 'Games Test', + level: 5, + xp: 100, + maxXp: 200, + coins: 1000, + cash: 20_000, + goldGrams: 5, + missionProgress: 4, + missionTarget: 20, + completedChallenges: [], + completedLessons: [], + completedGames: [], + claimedRewards: [], + ownedItems: ['suit'], + achievements: [ + { id: 'first-gold', title: 't', description: 'd', icon: '🥇', unlocked: true }, + ], + mapLevels: [{ id: 2, title: 't', stars: 1, maxStars: 3, status: 'completed', x: 1, y: 2 }], + totalStars: 1, + maxStars: 39, + marketSelectedRange: '1d', + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + gamesUserId, + JSON.stringify(gamesState), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), gamesUser, gamesHash, gamesUserId, now) + + const gamesLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: gamesUser, password: 'gamesPass1' }, + }) + assert.equal(gamesLogin.statusCode, 200) + const gamesToken = parseJson<{ token: string }>(gamesLogin.payload).token + + const gamesUnauthGet = await app.inject({ method: 'GET', url: '/api/games' }) + assert.equal(gamesUnauthGet.statusCode, 401) + const gamesUnauthPost = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + payload: {}, + }) + assert.equal(gamesUnauthPost.statusCode, 401) + + const gamesListRes = await app.inject({ + method: 'GET', + url: '/api/games', + headers: authHeader(gamesToken), + }) + assert.equal(gamesListRes.statusCode, 200) + const gamesList = parseJson< + Array<{ id: string; route?: string; rewardCoins?: number; rewardXp?: number }> + >(gamesListRes.payload) + assert.equal(gamesList.length, 6) + assert.deepEqual( + gamesList.map((g) => g.id), + GAMES.map((g) => g.id), + ) + assert.equal(gamesList[0].route, '/games/gold-guess') + assert.equal(gamesList[0].rewardCoins, 20) + assert.equal(gamesList[0].rewardXp, 50) + assert.equal(gamesList[1].route, '/lessons/inflation') + assert.equal(gamesList[1].rewardXp, 40) + assert.equal(gamesList[1].rewardCoins, undefined) + + const completeGoldGuess = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: { ...authHeader(gamesToken), 'content-type': 'application/json' }, + payload: JSON.stringify({}), + }) + assert.equal(completeGoldGuess.statusCode, 200) + const goldGuessBody = parseJson<{ + user: { coins: number; xp: number; level: number; missionProgress: number } + progress: { claimedRewards: string[]; completedGames: string[]; ownedItems: string[] } + transactions: Array<{ type: string; title: string; amount: number; unit: string }> + achievements: Array<{ id: string; unlocked: boolean }> + }>(completeGoldGuess.payload) + assert.equal(goldGuessBody.user.coins, 1020) + assert.equal(goldGuessBody.user.xp, 150) + assert.equal(goldGuessBody.user.level, 5) + assert.deepEqual(goldGuessBody.progress.claimedRewards, ['gold-guess']) + assert.deepEqual(goldGuessBody.progress.completedGames, ['gold-guess']) + assert.equal(goldGuessBody.transactions[0].type, 'income') + assert.equal(goldGuessBody.transactions[0].title, 'پاداش بازی') + assert.equal(goldGuessBody.transactions[0].amount, 20) + assert.equal(goldGuessBody.transactions[0].unit, 'سکه') + assert.equal(goldGuessBody.user.missionProgress, 4) + assert.deepEqual(goldGuessBody.progress.ownedItems, ['suit']) + assert.equal(goldGuessBody.achievements[0].unlocked, true) + + const meAfterGame = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(gamesToken), + }) + assert.equal(meAfterGame.statusCode, 200) + const meGameBody = parseJson<{ + user: { coins: number; xp: number } + progress: { claimedRewards: string[] } + }>(meAfterGame.payload) + assert.equal(meGameBody.user.coins, 1020) + assert.equal(meGameBody.user.xp, 150) + assert.deepEqual(meGameBody.progress.claimedRewards, ['gold-guess']) + + const stateBeforeGameDuplicate = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(gamesUserId) as { + state_json: string + } + ).state_json + + const duplicateGoldGuess = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: authHeader(gamesToken), + payload: {}, + }) + assert.equal(duplicateGoldGuess.statusCode, 200) + const duplicateBody = parseJson<{ user: { coins: number; xp: number } }>(duplicateGoldGuess.payload) + assert.equal(duplicateBody.user.coins, 1020) + assert.equal(duplicateBody.user.xp, 150) + const stateAfterDuplicateGame = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(gamesUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterDuplicateGame, stateBeforeGameDuplicate) + + const completeInflation = await app.inject({ + method: 'POST', + url: '/api/games/inflation/complete', + headers: authHeader(gamesToken), + payload: {}, + }) + assert.equal(completeInflation.statusCode, 200) + const inflationBody = parseJson<{ + user: { coins: number; xp: number } + progress: { claimedRewards: string[]; completedGames: string[] } + transactions: Array<{ title: string }> + }>(completeInflation.payload) + assert.equal(inflationBody.user.coins, 1020) + assert.equal(inflationBody.user.xp, 190) + assert.ok(inflationBody.progress.claimedRewards.includes('inflation')) + assert.ok(inflationBody.progress.completedGames.includes('inflation')) + assert.equal(inflationBody.transactions.length, 1) + + const completePortfolio = await app.inject({ + method: 'POST', + url: '/api/games/portfolio/complete', + headers: authHeader(gamesToken), + payload: {}, + }) + assert.equal(completePortfolio.statusCode, 200) + const portfolioBody = parseJson<{ + user: { coins: number; xp: number } + progress: { claimedRewards: string[]; completedGames: string[] } + transactions: unknown[] + }>(completePortfolio.payload) + assert.equal(portfolioBody.user.coins, 1020) + assert.equal(portfolioBody.user.xp, 190) + assert.ok(portfolioBody.progress.claimedRewards.includes('portfolio')) + assert.ok(portfolioBody.progress.completedGames.includes('portfolio')) + assert.equal(portfolioBody.transactions.length, 1) + + const levelUpUserId = randomUUID() + const levelUpState = { + ...gamesState, + id: levelUpUserId, + xp: 950, + maxXp: 100, + level: 1, + coins: 500, + claimedRewards: [], + completedGames: [], + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + levelUpUserId, + JSON.stringify(levelUpState), + now, + ) + const levelUpToken = jwtService.sign({ userId: levelUpUserId }) + + const levelUpComplete = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: authHeader(levelUpToken), + payload: {}, + }) + assert.equal(levelUpComplete.statusCode, 200) + const levelUpBody = parseJson<{ user: { xp: number; level: number; maxXp: number; coins: number } }>( + levelUpComplete.payload, + ) + assert.equal(levelUpBody.user.coins, 520) + assert.equal(levelUpBody.user.level, 7) + assert.equal(levelUpBody.user.xp, 125) + assert.equal(levelUpBody.user.maxXp, 231) + + const rollbackUserId = randomUUID() + const rollbackState = { + id: rollbackUserId, + name: 'Rollback Test', + level: 3, + xp: 40, + maxXp: 120, + coins: 777, + cash: 12_345, + goldGrams: 3.5, + missionProgress: 2, + missionTarget: 15, + completedChallenges: ['daily-challenge'], + completedLessons: ['lesson-x'], + completedGames: [], + claimedRewards: [], + ownedItems: ['castle'], + achievements: [ + { id: 'saver', title: 't', description: 'd', icon: '💰', unlocked: true }, + ], + mapLevels: [{ id: 3, title: 'map', stars: 2, maxStars: 3, status: 'current', x: 5, y: 6 }], + totalStars: 2, + maxStars: 39, + marketSelectedRange: '30d', + transactions: [ + { id: 'rb-1', type: 'income', title: 'seed tx', amount: 10, unit: 'سکه', date: '۱۴۰۴/۰۲/۰۱' }, + ], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + rollbackUserId, + JSON.stringify(rollbackState), + now, + ) + const rollbackToken = jwtService.sign({ userId: rollbackUserId }) + const rollbackDb = dbService.getDb() + const stateBeforeRollback = ( + rollbackDb.prepare('SELECT state_json FROM users WHERE id = ?').get(rollbackUserId) as { + state_json: string + } + ).state_json + + rollbackDb.exec(` + CREATE TRIGGER test_abort_games_users_update + BEFORE UPDATE ON users + WHEN NEW.id = '${rollbackUserId}' + BEGIN + SELECT RAISE(ABORT, 'integration test rollback'); + END; + `) + + try { + const rollbackComplete = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: { ...authHeader(rollbackToken), 'content-type': 'application/json' }, + payload: JSON.stringify({}), + }) + assert.equal(rollbackComplete.statusCode, 500) + assert.deepEqual(parseJson(rollbackComplete.payload), { error: 'خطای سرور' }) + + const stateAfterRollback = ( + rollbackDb.prepare('SELECT state_json FROM users WHERE id = ?').get(rollbackUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterRollback, stateBeforeRollback) + + const beforeParsed = JSON.parse(stateBeforeRollback) as typeof rollbackState + const afterParsed = JSON.parse(stateAfterRollback) as typeof rollbackState + assert.equal(afterParsed.coins, beforeParsed.coins) + assert.equal(afterParsed.xp, beforeParsed.xp) + assert.equal(afterParsed.level, beforeParsed.level) + assert.equal(afterParsed.maxXp, beforeParsed.maxXp) + assert.deepEqual(afterParsed.claimedRewards, beforeParsed.claimedRewards) + assert.deepEqual(afterParsed.completedGames, beforeParsed.completedGames) + assert.deepEqual(afterParsed.transactions, beforeParsed.transactions) + assert.equal(afterParsed.cash, beforeParsed.cash) + assert.equal(afterParsed.goldGrams, beforeParsed.goldGrams) + assert.deepEqual(afterParsed.ownedItems, beforeParsed.ownedItems) + assert.deepEqual(afterParsed.achievements, beforeParsed.achievements) + assert.equal(afterParsed.missionProgress, beforeParsed.missionProgress) + assert.deepEqual(afterParsed.mapLevels, beforeParsed.mapLevels) + } finally { + rollbackDb.exec('DROP TRIGGER IF EXISTS test_abort_games_users_update') + } + + const stateBeforeInvalid = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(gamesUserId) as { + state_json: string + } + ).state_json + + const invalidGame = await app.inject({ + method: 'POST', + url: '/api/games/not-a-game/complete', + headers: authHeader(gamesToken), + payload: {}, + }) + assert.equal(invalidGame.statusCode, 404) + assert.deepEqual(parseJson(invalidGame.payload), { error: 'بازی پیدا نشد' }) + const stateAfterInvalid = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(gamesUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterInvalid, stateBeforeInvalid) + + const extraFieldGame = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: { ...authHeader(gamesToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ score: 100 }), + }) + assert.equal(extraFieldGame.statusCode, 400) + + const rewardFieldGame = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: { ...authHeader(gamesToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ reward: 999 }), + }) + assert.equal(rewardFieldGame.statusCode, 400) + + // --- Phase F3: Daily Challenge integration --- + const { DAILY_CHALLENGE, CHALLENGE_OPTIONS } = await import( + '../src/shared/catalog/challenge.constants' + ) + + const challengeUser = `challenge_${randomUUID().slice(0, 8)}` + const challengeUserId = randomUUID() + const challengeHash = bcrypt.hashSync('challengePass1', 10) + const challengeState = { + id: challengeUserId, + name: 'Challenge Test', + level: 4, + xp: 80, + maxXp: 100, + coins: 200, + cash: 5000, + goldGrams: 2, + missionProgress: 5, + missionTarget: 20, + completedChallenges: [] as string[], + completedLessons: [], + completedGames: ['game-a'], + claimedRewards: ['reward-a'], + ownedItems: ['pet'], + achievements: [{ id: 'saver', title: 't', description: 'd', icon: '💰', unlocked: true }], + mapLevels: [{ id: 1, title: 't', stars: 1, maxStars: 3, status: 'completed', x: 1, y: 2 }], + totalStars: 1, + maxStars: 39, + marketSelectedRange: '7d', + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + challengeUserId, + JSON.stringify(challengeState), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), challengeUser, challengeHash, challengeUserId, now) + + const challengeLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: challengeUser, password: 'challengePass1' }, + }) + assert.equal(challengeLogin.statusCode, 200) + const challengeToken = parseJson<{ token: string }>(challengeLogin.payload).token + + const challengeUnauthGet = await app.inject({ method: 'GET', url: '/api/challenges/daily' }) + assert.equal(challengeUnauthGet.statusCode, 401) + const challengeUnauthPost = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + payload: { optionId: 'gold' }, + }) + assert.equal(challengeUnauthPost.statusCode, 401) + + const dailyGet = await app.inject({ + method: 'GET', + url: '/api/challenges/daily', + headers: authHeader(challengeToken), + }) + assert.equal(dailyGet.statusCode, 200) + const dailyBody = parseJson<{ + id: string + question: string + coinReward: number + xpReward: number + completed: boolean + options: Array<{ id: string; label: string; icon: string; color: string }> + }>(dailyGet.payload) + assert.equal(dailyBody.id, DAILY_CHALLENGE.id) + assert.equal(dailyBody.question, DAILY_CHALLENGE.question) + assert.equal(dailyBody.coinReward, 50) + assert.equal(dailyBody.xpReward, 30) + assert.equal(dailyBody.completed, false) + assert.deepEqual( + dailyBody.options.map((o) => o.id), + CHALLENGE_OPTIONS.map((o) => o.id), + ) + assert.ok(!dailyGet.payload.includes('isCorrect')) + assert.ok(!dailyGet.payload.includes('feedback')) + assert.equal(dailyBody.options[0].label, 'طلا') + + const completeGold = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(challengeToken), + payload: { optionId: 'gold' }, + }) + assert.equal(completeGold.statusCode, 200) + const goldBody = parseJson<{ + user: { coins: number; xp: number; level: number; missionProgress: number } + progress: { completedChallenges: string[]; claimedRewards: string[]; completedGames: string[]; ownedItems: string[] } + transactions: Array<{ type: string; title: string; amount: number; unit: string }> + achievements: Array<{ id: string; unlocked: boolean }> + feedback: string + alreadyCompleted: boolean + }>(completeGold.payload) + assert.equal(goldBody.user.coins, 250) + assert.equal(goldBody.user.xp, 10) + assert.equal(goldBody.user.level, 5) + assert.equal(goldBody.user.missionProgress, 6) + assert.deepEqual(goldBody.progress.completedChallenges, ['daily-challenge']) + assert.equal(goldBody.transactions[0].type, 'income') + assert.equal(goldBody.transactions[0].title, 'چالش روزانه') + assert.equal(goldBody.transactions[0].amount, 50) + assert.equal(goldBody.transactions[0].unit, 'سکه') + assert.equal(goldBody.feedback, CHALLENGE_OPTIONS[0].feedback) + assert.equal(goldBody.alreadyCompleted, false) + assert.deepEqual(goldBody.progress.claimedRewards, ['reward-a']) + assert.deepEqual(goldBody.progress.completedGames, ['game-a']) + assert.deepEqual(goldBody.progress.ownedItems, ['pet']) + assert.equal(goldBody.achievements[0].unlocked, true) + + const dailyAfter = await app.inject({ + method: 'GET', + url: '/api/challenges/daily', + headers: authHeader(challengeToken), + }) + assert.equal(parseJson<{ completed: boolean }>(dailyAfter.payload).completed, true) + + const meAfterChallenge = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(challengeToken), + }) + assert.equal(meAfterChallenge.statusCode, 200) + const meChallengeBody = parseJson<{ + user: { coins: number; xp: number } + progress: { completedChallenges: string[] } + }>(meAfterChallenge.payload) + assert.equal(meChallengeBody.user.coins, 250) + assert.equal(meChallengeBody.user.xp, 10) + assert.deepEqual(meChallengeBody.progress.completedChallenges, ['daily-challenge']) + + const stateBeforeChallengeDuplicate = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(challengeUserId) as { + state_json: string + } + ).state_json + + const duplicateChallenge = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(challengeToken), + payload: { optionId: 'gold' }, + }) + assert.equal(duplicateChallenge.statusCode, 200) + const duplicateChallengeBody = parseJson<{ + user: { coins: number; xp: number; missionProgress: number } + feedback: string + alreadyCompleted: boolean + }>(duplicateChallenge.payload) + assert.equal(duplicateChallengeBody.alreadyCompleted, true) + assert.equal(duplicateChallengeBody.user.coins, 250) + assert.equal(duplicateChallengeBody.user.xp, 10) + assert.equal(duplicateChallengeBody.user.missionProgress, 6) + assert.equal(duplicateChallengeBody.feedback, CHALLENGE_OPTIONS[0].feedback) + const stateAfterChallengeDuplicate = ( + dbService.getDb().prepare('SELECT state_json FROM users WHERE id = ?').get(challengeUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterChallengeDuplicate, stateBeforeChallengeDuplicate) + + const wrongUserId = randomUUID() + const wrongState = { + ...challengeState, + id: wrongUserId, + completedChallenges: [], + coins: 300, + xp: 70, + maxXp: 100, + level: 4, + missionProgress: 2, + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + wrongUserId, + JSON.stringify(wrongState), + now, + ) + const wrongToken = jwtService.sign({ userId: wrongUserId }) + + const completeWrong = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(wrongToken), + payload: { optionId: 'phone' }, + }) + assert.equal(completeWrong.statusCode, 200) + const wrongBody = parseJson<{ + user: { coins: number; xp: number; missionProgress: number } + progress: { completedChallenges: string[] } + feedback: string + alreadyCompleted: boolean + }>(completeWrong.payload) + assert.equal(wrongBody.user.coins, 350) + assert.equal(wrongBody.user.xp, 0) + assert.equal(wrongBody.user.level, 5) + assert.equal(wrongBody.user.missionProgress, 3) + assert.deepEqual(wrongBody.progress.completedChallenges, ['daily-challenge']) + assert.equal(wrongBody.feedback, CHALLENGE_OPTIONS[3].feedback) + assert.equal(wrongBody.alreadyCompleted, false) + + const invalidOption = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(wrongToken), + payload: { optionId: 'invalid' }, + }) + assert.equal(invalidOption.statusCode, 400) + assert.deepEqual(parseJson(invalidOption.payload), { error: 'گزینه نامعتبر است' }) + + const emptyOption = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(wrongToken), + payload: { optionId: '' }, + }) + assert.equal(emptyOption.statusCode, 400) + + const emptyBodyChallenge = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: { ...authHeader(wrongToken), 'content-type': 'application/json' }, + payload: JSON.stringify({}), + }) + assert.equal(emptyBodyChallenge.statusCode, 400) + + const extraFieldChallenge = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: { ...authHeader(challengeToken), 'content-type': 'application/json' }, + payload: JSON.stringify({ optionId: 'gold', coins: 999 }), + }) + assert.equal(extraFieldChallenge.statusCode, 400) + + const rollbackChallengeUserId = randomUUID() + const rollbackChallengeState = { + ...challengeState, + id: rollbackChallengeUserId, + completedChallenges: [], + coins: 400, + xp: 90, + maxXp: 100, + level: 4, + missionProgress: 1, + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + rollbackChallengeUserId, + JSON.stringify(rollbackChallengeState), + now, + ) + const rollbackChallengeToken = jwtService.sign({ userId: rollbackChallengeUserId }) + const rollbackChallengeDb = dbService.getDb() + const stateBeforeChallengeRollback = ( + rollbackChallengeDb.prepare('SELECT state_json FROM users WHERE id = ?').get(rollbackChallengeUserId) as { + state_json: string + } + ).state_json + + rollbackChallengeDb.exec(` + CREATE TRIGGER test_abort_challenge_users_update + BEFORE UPDATE ON users + WHEN NEW.id = '${rollbackChallengeUserId}' + BEGIN + SELECT RAISE(ABORT, 'integration test challenge rollback'); + END; + `) + + try { + const rollbackChallengeComplete = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: authHeader(rollbackChallengeToken), + payload: { optionId: 'gold' }, + }) + assert.equal(rollbackChallengeComplete.statusCode, 500) + assert.deepEqual(parseJson(rollbackChallengeComplete.payload), { error: 'خطای سرور' }) + + const stateAfterChallengeRollback = ( + rollbackChallengeDb.prepare('SELECT state_json FROM users WHERE id = ?').get(rollbackChallengeUserId) as { + state_json: string + } + ).state_json + assert.equal(stateAfterChallengeRollback, stateBeforeChallengeRollback) + + const beforeRb = JSON.parse(stateBeforeChallengeRollback) as typeof rollbackChallengeState + const afterRb = JSON.parse(stateAfterChallengeRollback) as typeof rollbackChallengeState + assert.equal(afterRb.coins, beforeRb.coins) + assert.equal(afterRb.xp, beforeRb.xp) + assert.equal(afterRb.level, beforeRb.level) + assert.equal(afterRb.maxXp, beforeRb.maxXp) + assert.equal(afterRb.missionProgress, beforeRb.missionProgress) + assert.deepEqual(afterRb.completedChallenges, beforeRb.completedChallenges) + assert.deepEqual(afterRb.transactions, beforeRb.transactions) + } finally { + rollbackChallengeDb.exec('DROP TRIGGER IF EXISTS test_abort_challenge_users_update') + } + + // --- Phase F4: Achievements integration --- + const { ACHIEVEMENTS: achievementCatalog } = await import( + '../src/shared/catalog/achievements.constants' + ) + const expectedDefinitionOrder = ['first-gold', 'saver', 'market', 'inflation'] + + const f4User = `f4_${randomUUID().slice(0, 8)}` + const f4UserId = randomUUID() + const f4Hash = bcrypt.hashSync('f4Pass123', 10) + const f4State = { + id: f4UserId, + name: 'F4 Test', + level: 5, + xp: 100, + maxXp: 200, + coins: 10_000, + cash: 10_000_000, + goldGrams: 0, + missionProgress: 3, + missionTarget: 20, + completedChallenges: [], + completedLessons: [], + completedGames: [], + claimedRewards: [], + ownedItems: [], + achievements: [ + { id: 'first-gold', title: 'اولین خرید طلا', description: 'd1', icon: '🥇', unlocked: false }, + { id: 'saver', title: 'پس‌اندازگر کوچک', description: 'd2', icon: '💰', unlocked: true }, + { id: 'market', title: 'شاگرد بازار', description: 'd3', icon: '📊', unlocked: false }, + { id: 'inflation', title: 'شکست‌دهنده تورم', description: 'd4', icon: '👾', unlocked: false }, + ], + mapLevels: [{ id: 1, title: 'تست', stars: 1, maxStars: 3, status: 'current', x: 10, y: 20 }], + totalStars: 1, + maxStars: 39, + marketSelectedRange: '1d', + transactions: [], + } + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + f4UserId, + JSON.stringify(f4State), + now, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), f4User, f4Hash, f4UserId, now) + + const f4Login = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: f4User, password: 'f4Pass123' }, + }) + assert.equal(f4Login.statusCode, 200) + const f4Token = parseJson<{ token: string }>(f4Login.payload).token + + const achUnauth = await app.inject({ method: 'GET', url: '/api/achievements' }) + assert.equal(achUnauth.statusCode, 401) + assert.deepEqual(parseJson(achUnauth.payload), { error: 'لطفاً وارد شوید' }) + + const achBadToken = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: { authorization: 'Bearer not-a-valid-jwt' }, + }) + assert.equal(achBadToken.statusCode, 401) + assert.deepEqual(parseJson(achBadToken.payload), { + error: 'نشست نامعتبر است. دوباره وارد شوید', + }) + + const rowBeforeAch = dbService + .getDb() + .prepare('SELECT state_json, updated_at FROM users WHERE id = ?') + .get(f4UserId) as { state_json: string; updated_at: string } + + const achRes = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(f4Token), + }) + assert.equal(achRes.statusCode, 200) + const achBody = parseJson<{ + definitions: Array<{ id: string; title: string; description: string; icon: string; unlocked?: boolean }> + unlocked: Array<{ id: string; title: string; description: string; icon: string; unlocked: boolean }> + }>(achRes.payload) + + assert.ok(Array.isArray(achBody.definitions)) + assert.ok(Array.isArray(achBody.unlocked)) + assert.equal(achBody.definitions.length, achievementCatalog.length) + assert.deepEqual( + achBody.definitions.map((d) => d.id), + expectedDefinitionOrder, + ) + for (const def of achBody.definitions) { + assert.equal(typeof def.id, 'string') + assert.equal(typeof def.title, 'string') + assert.equal(typeof def.description, 'string') + assert.equal(typeof def.icon, 'string') + assert.equal('unlocked' in def, false) + } + assert.deepEqual( + achBody.definitions.map(({ id, title, description, icon }) => ({ id, title, description, icon })), + achievementCatalog.map(({ id, title, description, icon }) => ({ id, title, description, icon })), + ) + assert.deepEqual(achBody.unlocked, f4State.achievements) + assert.equal(achBody.unlocked.filter((a) => !a.unlocked).length, 3) + assert.equal(achBody.unlocked.find((a) => a.id === 'saver')?.unlocked, true) + + const rowAfterAch = dbService + .getDb() + .prepare('SELECT state_json, updated_at FROM users WHERE id = ?') + .get(f4UserId) as { state_json: string; updated_at: string } + assert.equal(rowAfterAch.state_json, rowBeforeAch.state_json) + assert.equal(rowAfterAch.updated_at, rowBeforeAch.updated_at) + + const catalogSnapshot = achievementCatalog.map((a) => ({ ...a })) + achBody.definitions[0].title = 'mutated' + assert.deepEqual( + achievementCatalog.map(({ id, title, description, icon }) => ({ id, title, description, icon })), + catalogSnapshot, + ) + + const { createDefaultUserState: createDefaultUserStateF0 } = await import( + '../src/shared/catalog/default-user-state' + ) + const defaultState1 = createDefaultUserState('f4-default-1') + const defaultState2 = createDefaultUserState('f4-default-2') + assert.notEqual(defaultState1.achievements, defaultState2.achievements) + assert.equal(defaultState1.achievements.length, achievementCatalog.length) + assert.deepEqual( + defaultState1.achievements.map((a) => a.id), + expectedDefinitionOrder, + ) + for (const achievement of defaultState1.achievements) { + const expected = + achievement.id === 'first-gold' || achievement.id === 'saver' + ? true + : false + assert.equal(achievement.unlocked, expected) + } + defaultState1.achievements[0].unlocked = false + assert.equal(defaultState2.achievements[0].unlocked, true) + + const buyBeforeAch = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(f4Token), + }) + const buyBeforeBody = parseJson<{ unlocked: Array<{ id: string; unlocked: boolean }> }>( + buyBeforeAch.payload, + ) + assert.equal(buyBeforeBody.unlocked.find((a) => a.id === 'first-gold')?.unlocked, false) + + const f4BuyRes = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: { ...authHeader(f4Token), 'content-type': 'application/json' }, + payload: JSON.stringify({ grams: 0.01 }), + }) + assert.equal(f4BuyRes.statusCode, 200) + const f4BuyBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f4BuyRes.payload, + ) + assert.equal(f4BuyBody.achievements.find((a) => a.id === 'first-gold')?.unlocked, true) + + const buyAfterAch = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(f4Token), + }) + const buyAfterBody = parseJson<{ unlocked: Array<{ id: string; unlocked: boolean }> }>( + buyAfterAch.payload, + ) + assert.equal(buyAfterBody.unlocked.find((a) => a.id === 'first-gold')?.unlocked, true) + + const f4MeRes = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(f4Token), + }) + const f4MeBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f4MeRes.payload, + ) + assert.equal(f4MeBody.achievements.find((a) => a.id === 'first-gold')?.unlocked, true) + + const { unlockAchievement: unlockAchievementOnce } = await import( + '../src/shared/economy/user-economy.helpers' + ) + const dupState = createDefaultUserState('f4-dup') + const dupCountBefore = dupState.achievements.length + unlockAchievementOnce(dupState, 'first-gold') + unlockAchievementOnce(dupState, 'first-gold') + assert.equal(dupState.achievements.length, dupCountBefore) + assert.equal(dupState.achievements.filter((a) => a.id === 'first-gold').length, 1) + + const f4StoreRes = await app.inject({ + method: 'POST', + url: '/api/store/purchases', + headers: { ...authHeader(f4Token), 'content-type': 'application/json' }, + payload: JSON.stringify({ productId: 'boost-xp' }), + }) + assert.equal(f4StoreRes.statusCode, 200) + const f4StoreBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f4StoreRes.payload, + ) + assert.equal(f4StoreBody.achievements.length, f4State.achievements.length) + assert.equal(f4StoreBody.achievements.find((a) => a.id === 'market')?.unlocked, false) + + const f4GameRes = await app.inject({ + method: 'POST', + url: '/api/games/gold-guess/complete', + headers: { ...authHeader(f4Token), 'content-type': 'application/json' }, + payload: JSON.stringify({}), + }) + assert.equal(f4GameRes.statusCode, 200) + const f4GameBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f4GameRes.payload, + ) + assert.equal(f4GameBody.achievements.find((a) => a.id === 'market')?.unlocked, false) + + const f4ChallengeRes = await app.inject({ + method: 'POST', + url: '/api/challenges/daily/complete', + headers: { ...authHeader(f4Token), 'content-type': 'application/json' }, + payload: JSON.stringify({ optionId: 'gold' }), + }) + assert.equal(f4ChallengeRes.statusCode, 200) + const f4ChallengeBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f4ChallengeRes.payload, + ) + assert.equal(f4ChallengeBody.achievements.find((a) => a.id === 'saver')?.unlocked, true) + assert.equal(f4ChallengeBody.achievements.find((a) => a.id === 'market')?.unlocked, false) + + const f4FinalRow = dbService + .getDb() + .prepare('SELECT state_json FROM users WHERE id = ?') + .get(f4UserId) as { state_json: string } + const f4FinalState = JSON.parse(f4FinalRow.state_json) as typeof f4State + assert.ok(typeof f4FinalState.coins === 'number') + assert.ok(typeof f4FinalState.xp === 'number') + assert.ok(typeof f4FinalState.level === 'number') + assert.ok(typeof f4FinalState.maxXp === 'number') + assert.ok(Array.isArray(f4FinalState.ownedItems)) + assert.ok(Array.isArray(f4FinalState.completedGames)) + assert.ok(Array.isArray(f4FinalState.completedChallenges)) + assert.ok(Array.isArray(f4FinalState.transactions)) + assert.ok(Array.isArray(f4FinalState.mapLevels)) + assert.equal(f4FinalState.achievements.find((a) => a.id === 'market')?.unlocked, false) + + const nestRoutes = [ + 'GET /health', + 'POST /api/auth/register', + 'POST /api/auth/login', + 'GET /api/me', + 'PATCH /api/me/profile', + 'PATCH /api/me/password', + 'GET /api/wallet/transactions', + 'POST /api/wallet/gold/buy', + 'POST /api/wallet/gold/sell', + 'GET /api/market/gold', + 'PATCH /api/market/preferences', + 'GET /api/gold-land/levels', + 'GET /api/gold-land/progress', + 'GET /api/gold-land/levels/:levelId', + 'POST /api/gold-land/levels/:levelId/start', + 'POST /api/gold-land/levels/:levelId/complete', + 'GET /api/store/products', + 'POST /api/store/purchases', + 'GET /api/games', + 'POST /api/games/:gameId/complete', + 'GET /api/challenges/daily', + 'POST /api/challenges/daily/complete', + 'GET /api/achievements', + ] + assert.equal(nestRoutes.length, 23) + assert.ok(nestRoutes.includes('GET /api/achievements')) + assert.equal( + nestRoutes.filter((r) => r.startsWith('GET /api/achievements') || r.startsWith('POST /api/achievements')).length, + 1, + ) + + // --- Phase F4.1: Default achievement seed parity --- + const { DEFAULT_ACHIEVEMENT_UNLOCK_STATE, createDefaultAchievements } = await import( + '../src/shared/catalog/achievements.constants' + ) + assert.deepEqual(DEFAULT_ACHIEVEMENT_UNLOCK_STATE, { + 'first-gold': true, + saver: true, + market: false, + inflation: false, + }) + + const f41RegisterUser = `f41_reg_${randomUUID().slice(0, 8)}` + const f41RegisterRes = await app.inject({ + method: 'POST', + url: '/api/auth/register', + payload: { username: f41RegisterUser, password: 'secret12', name: 'F41 Register' }, + }) + assert.equal(f41RegisterRes.statusCode, 200) + const f41RegisterBody = parseJson<{ + token: string + me: { achievements: Array<{ id: string; unlocked: boolean }> } + }>(f41RegisterRes.payload) + assertFastifyDefaultAchievementSeed(f41RegisterBody.me.achievements) + + const f41RegisterAch = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(f41RegisterBody.token), + }) + assert.equal(f41RegisterAch.statusCode, 200) + const f41RegisterAchBody = parseJson<{ + definitions: Array<{ id: string; unlocked?: boolean }> + unlocked: Array<{ id: string; unlocked: boolean }> + }>(f41RegisterAch.payload) + assertFastifyDefaultAchievementSeed(f41RegisterAchBody.unlocked) + for (const def of f41RegisterAchBody.definitions) { + assert.equal('unlocked' in def, false) + } + + const f41DemoAchRes = await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(demoBody.token), + }) + assert.equal(f41DemoAchRes.statusCode, 200) + const f41DemoAchBody = parseJson<{ + unlocked: Array<{ id: string; unlocked: boolean }> + }>(f41DemoAchRes.payload) + assertFastifyDefaultAchievementSeed(f41DemoAchBody.unlocked) + + const f41DemoMeRes = await app.inject({ + method: 'GET', + url: '/api/me', + headers: authHeader(demoBody.token), + }) + const f41DemoMeBody = parseJson<{ + achievements: Array<{ id: string; unlocked: boolean }> + }>(f41DemoMeRes.payload) + assertFastifyDefaultAchievementSeed(f41DemoMeBody.achievements) + + const f41EnsureAuth = `f41_ensure_${randomUUID().slice(0, 8)}` + const f41EnsureUserId = randomUUID() + const f41EnsureHash = bcrypt.hashSync('ensurePass1', 10) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), f41EnsureAuth, f41EnsureHash, f41EnsureUserId, now) + const f41EnsureLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: f41EnsureAuth, password: 'ensurePass1' }, + }) + assert.equal(f41EnsureLogin.statusCode, 200) + const f41EnsureBody = parseJson<{ + me: { achievements: Array<{ id: string; unlocked: boolean }> } + }>(f41EnsureLogin.payload) + assertFastifyDefaultAchievementSeed(f41EnsureBody.me.achievements) + + const f41DefaultA = createDefaultAchievements() + const f41DefaultB = createDefaultAchievements() + assert.notEqual(f41DefaultA, f41DefaultB) + assert.notEqual(f41DefaultA[0], f41DefaultB[0]) + f41DefaultA[0].unlocked = false + assert.equal(f41DefaultB[0].unlocked, true) + + const f41LegacyUserId = randomUUID() + const f41LegacyState = { + id: f41LegacyUserId, + name: 'Legacy Seed', + level: 1, + xp: 0, + maxXp: 100, + coins: 50, + cash: 1000, + goldGrams: 0, + missionProgress: 0, + missionTarget: 3, + completedChallenges: [], + completedLessons: [], + completedGames: [], + claimedRewards: [], + ownedItems: [], + achievements: [{ id: 'custom-only', title: 'Custom', description: 'x', icon: '⭐', unlocked: true }], + mapLevels: [], + totalStars: 0, + maxStars: 39, + marketSelectedRange: '7d', + transactions: [], + } + const f41LegacyAuth = `f41_legacy_${randomUUID().slice(0, 8)}` + const f41LegacyJson = JSON.stringify(f41LegacyState) + const f41LegacyUpdatedAt = '2020-01-01T00:00:00.000Z' + dbService.getDb().prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + f41LegacyUserId, + f41LegacyJson, + f41LegacyUpdatedAt, + ) + dbService.getDb().prepare( + 'INSERT INTO auth_accounts (id, username, password_hash, user_id, created_at) VALUES (?, ?, ?, ?, ?)', + ).run(randomUUID(), f41LegacyAuth, bcrypt.hashSync('legacyPass1', 10), f41LegacyUserId, now) + const f41LegacyLogin = await app.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { username: f41LegacyAuth, password: 'legacyPass1' }, + }) + const f41LegacyToken = parseJson<{ token: string }>(f41LegacyLogin.payload).token + await app.inject({ + method: 'GET', + url: '/api/achievements', + headers: authHeader(f41LegacyToken), + }) + const f41LegacyRow = dbService + .getDb() + .prepare('SELECT state_json, updated_at FROM users WHERE id = ?') + .get(f41LegacyUserId) as { state_json: string; updated_at: string } + assert.equal(f41LegacyRow.state_json, f41LegacyJson) + assert.equal(f41LegacyRow.updated_at, f41LegacyUpdatedAt) + + const f41DefaultBuyUser = `f41_buy_${randomUUID().slice(0, 8)}` + const f41DefaultBuyRes = await app.inject({ + method: 'POST', + url: '/api/auth/register', + payload: { username: f41DefaultBuyUser, password: 'secret12', name: 'F41 Buy' }, + }) + const f41DefaultBuyBody = parseJson<{ + token: string + me: { achievements: Array<{ id: string; unlocked: boolean }> } + }>(f41DefaultBuyRes.payload) + assert.equal(f41DefaultBuyBody.me.achievements.find((a) => a.id === 'first-gold')?.unlocked, true) + const f41DefaultBuyAchCount = f41DefaultBuyBody.me.achievements.length + const f41GoldBuyRes = await app.inject({ + method: 'POST', + url: '/api/wallet/gold/buy', + headers: { + ...authHeader(f41DefaultBuyBody.token), + 'content-type': 'application/json', + }, + payload: JSON.stringify({ grams: 0.01 }), + }) + assert.equal(f41GoldBuyRes.statusCode, 200) + const f41GoldBuyBody = parseJson<{ achievements: Array<{ id: string; unlocked: boolean }> }>( + f41GoldBuyRes.payload, + ) + assert.equal(f41GoldBuyBody.achievements.length, f41DefaultBuyAchCount) + assert.equal(f41GoldBuyBody.achievements.filter((a) => a.id === 'first-gold').length, 1) + assert.equal(f41GoldBuyBody.achievements.find((a) => a.id === 'first-gold')?.unlocked, true) + + // --- Phase F0: shared economy helpers regression --- + const { + addXp, + addTransaction, + incrementMission, + unlockAchievement, + } = await import('../src/shared/economy/user-economy.helpers') + const { createDefaultUserState: createDefaultUserStateF0 } = await import( + '../src/shared/catalog/default-user-state' + ) + + const xpState = createDefaultUserStateF0('f0-xp') + xpState.xp = 950 + xpState.maxXp = 100 + xpState.level = 1 + addXp(xpState, 200) + assert.equal(xpState.level, 8) + assert.equal(xpState.xp, 44) + assert.equal(xpState.maxXp, 266) + + const txCapState = createDefaultUserStateF0('f0-tx') + txCapState.transactions = [] + for (let i = 0; i < 55; i++) { + addTransaction(txCapState, { + type: 'income', + title: `tx-${i}`, + amount: 1, + unit: 'سکه', + }) + } + assert.equal(txCapState.transactions.length, 50) + assert.equal(txCapState.transactions[0].title, 'tx-54') + + const missionState = createDefaultUserStateF0('f0-mission') + missionState.missionProgress = 0 + missionState.missionTarget = 3 + incrementMission(missionState) + incrementMission(missionState) + incrementMission(missionState) + incrementMission(missionState) + assert.equal(missionState.missionProgress, 3) + + const achState = createDefaultUserStateF0('f0-ach') + const firstGold = achState.achievements.find((a) => a.id === 'first-gold') + assert.ok(firstGold) + assert.equal(firstGold!.unlocked, true) + firstGold!.unlocked = false + unlockAchievement(achState, 'first-gold') + unlockAchievement(achState, 'first-gold') + assert.equal(firstGold!.unlocked, true) + + const buyMissionBody = parseJson<{ user: { missionProgress: number } }>(buyRes.payload) + assert.equal(buyMissionBody.user.missionProgress, 4) + + // 23. shutdown closes DB connection + await app.close() + assert.throws(() => dbService.getDb(), /not initialized|Database is not initialized/) + + // idempotent migration on fresh app instance + const app2 = await createApp() + const secondMigration = app2.get(DatabaseService).verifyMigrationsApplied() + assert.equal(secondMigration.ok, true) + await app2.close() + + console.log('integration tests passed (Phase B + C + D + E + F0 + F1 + F2 + F3 + F4 + F4.1)') + } finally { + try { + rmSync(testDir, { recursive: true, force: true }) + } catch { + // ignore + } + } +} + +runTests().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/test/progression.test.ts b/test/progression.test.ts new file mode 100644 index 0000000..4542418 --- /dev/null +++ b/test/progression.test.ts @@ -0,0 +1,324 @@ +/** + * Gold Land progression tests — always uses a temporary SQLite file. + * Run: npm run test:progression + */ +import assert from 'node:assert/strict' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { ValidationPipe } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify' +import { ApiExceptionFilter } from '../src/common/filters/api-exception.filter' +import { getGradableQuestions } from '../src/gold-land/lessons/lesson.grader' +import type { LessonDefinition, LessonSubmissionAnswer } from '../src/gold-land/lessons/lesson.types' +import { ProgressionError } from '../src/gold-land/exceptions/progression.exception' +import { GoldLandProgressionService } from '../src/gold-land/services/progression.service' +import { RewardService } from '../src/gold-land/services/reward.service' +import type { DatabaseService } from '../src/database/database.service' +import { createDefaultUserState } from '../src/shared/catalog/default-user-state' + +const TEST_USER = 'progression-test-user' +const testDir = mkdtempSync(join(tmpdir(), 'gold-land-nest-progression-')) +process.env.DB_PATH = join(testDir, 'test.db') +process.env.NODE_ENV = 'test' +process.env.PORT = '3998' +process.env.JWT_SECRET = 'gold-land-dev-secret-change-in-production' + +async function createApp(): Promise { + const { AppModule } = await import('../src/app.module') + const moduleRef = await Test.createTestingModule({ + imports: [AppModule], + }).compile() + + const app = moduleRef.createNestApplication(new FastifyAdapter()) + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + }), + ) + app.useGlobalFilters(new ApiExceptionFilter()) + await app.init() + await app.getHttpAdapter().getInstance().ready() + return app +} + +function resetTestUser(dbService: DatabaseService) { + const db = dbService.getDb() + db.prepare('DELETE FROM users WHERE id = ?').run(TEST_USER) + db.prepare('DELETE FROM user_level_progress WHERE user_id = ?').run(TEST_USER) + db.prepare('DELETE FROM reward_ledger WHERE user_id = ?').run(TEST_USER) + + const state = createDefaultUserState(TEST_USER) + db.prepare('INSERT INTO users (id, state_json, updated_at) VALUES (?, ?, ?)').run( + TEST_USER, + JSON.stringify(state), + new Date().toISOString(), + ) +} + +function grantStars(dbService: DatabaseService, levelId: number, stars: number, score: number) { + const now = new Date().toISOString() + dbService.getDb().prepare(` + INSERT INTO user_level_progress ( + user_id, level_id, best_score, stars, attempts_count, started_at, completed_at, updated_at + ) VALUES (?, ?, ?, ?, 1, ?, ?, ?) + ON CONFLICT(user_id, level_id) DO UPDATE SET + best_score = excluded.best_score, + stars = excluded.stars, + completed_at = excluded.completed_at, + updated_at = excluded.updated_at + `).run(TEST_USER, levelId, score, stars, now, now, now) +} + +function buildAllCorrect(lesson: LessonDefinition): LessonSubmissionAnswer[] { + return getGradableQuestions(lesson).map(({ questionId, step }) => { + if (step.type === 'sort') { + return { questionId, orderedItemIds: [...step.correctOrder] } + } + return { questionId, answerId: step.correctAnswerId } + }) +} + +function buildPartialCorrect(lesson: LessonDefinition, correctCount: number): LessonSubmissionAnswer[] { + const gradable = getGradableQuestions(lesson) + return gradable.map(({ questionId, step }, index) => { + const isCorrect = index < correctCount + if (step.type === 'sort') { + const order = isCorrect ? [...step.correctOrder] : [...step.correctOrder].reverse() + return { questionId, orderedItemIds: order } + } + if (!isCorrect) { + const wrong = + step.type === 'true_false' + ? step.correctAnswerId === 'true' + ? 'false' + : 'true' + : step.options.find((o) => o.id !== step.correctAnswerId)?.id ?? 'wrong' + return { questionId, answerId: wrong } + } + return { questionId, answerId: step.correctAnswerId } + }) +} + +function expectError(fn: () => unknown, statusCode: number) { + try { + fn() + assert.fail('Expected error') + } catch (error) { + assert.ok(error instanceof ProgressionError) + assert.equal(error.statusCode, statusCode) + } +} + +async function runTests() { + const app = await createApp() + const dbService = (await import('../src/database/database.service')).DatabaseService + const database = app.get(dbService) + const progression = app.get(GoldLandProgressionService) + const rewards = app.get(RewardService) + + const { getAllPlayableLessons, countGradableQuestions } = await import( + '../src/gold-land/lessons/lesson.registry' + ) + const { loadLessonForLevel } = await import('../src/gold-land/lessons/lesson.loader') + const { sanitizeLesson } = await import('../src/gold-land/lessons/lesson.sanitizer') + const { gradeLessonSubmission, LessonGradingError } = await import( + '../src/gold-land/lessons/lesson.grader' + ) + const { inflationLesson } = await import('../src/gold-land/lessons/inflation.lesson') + const { needsAndWantsLesson } = await import('../src/gold-land/lessons/needs-and-wants.lesson') + const { isLevelUnlocked, starsFromScore } = await import( + '../src/gold-land/services/progression.service' + ) + + try { + resetTestUser(database) + let items = progression.buildLevelItems(TEST_USER) + + const playable = getAllPlayableLessons() + assert.equal(playable.length, 6) + for (const lesson of playable) { + assert.equal(countGradableQuestions(lesson), 5, `${lesson.id} should have 5 gradable questions`) + } + + assert.equal(isLevelUnlocked(1, new Map()), true) + assert.notEqual(items.find((l) => l.id === 1)?.status, 'locked') + assert.equal(items.find((l) => l.id === 2)?.status, 'locked') + assert.equal(items.find((l) => l.id === 6)?.status, 'locked') + + expectError(() => progression.startLevel(TEST_USER, 6), 403) + expectError( + () => progression.completeLevel(TEST_USER, 6, { answers: buildAllCorrect(inflationLesson) }), + 403, + ) + + for (let id = 1; id <= 5; id++) { + grantStars(database, id, 1, 65) + } + items = progression.buildLevelItems(TEST_USER) + assert.equal(items.find((l) => l.id === 6)?.status, 'available') + + const ALL_CORRECT_INFLATION = buildAllCorrect(inflationLesson) + const THREE_CORRECT_INFLATION = buildPartialCorrect(inflationLesson, 3) + const FOUR_CORRECT_INFLATION = buildPartialCorrect(inflationLesson, 4) + const ONE_CORRECT_INFLATION = buildPartialCorrect(inflationLesson, 1) + + try { + progression.completeLevel(TEST_USER, 6, { answers: [{ questionId: 'unknown-q', answerId: 'option-b' }] }) + assert.fail('expected invalid question') + } catch (error) { + assert.ok(error instanceof ProgressionError) + assert.equal(error.statusCode, 400) + } + + try { + progression.completeLevel(TEST_USER, 6, { + answers: [{ questionId: 'inflation-q1', answerId: 'invalid-option' }], + }) + assert.fail('expected invalid answer') + } catch (error) { + assert.ok(error instanceof ProgressionError) + assert.equal(error.statusCode, 400) + } + + try { + gradeLessonSubmission(inflationLesson, [ + { questionId: 'inflation-q1', answerId: 'option-b' }, + { questionId: 'inflation-q1', answerId: 'option-a' }, + ]) + assert.fail('expected duplicate questionId') + } catch (error) { + assert.ok(error instanceof LessonGradingError) + assert.equal(error.statusCode, 400) + } + + try { + gradeLessonSubmission(needsAndWantsLesson, [ + { + questionId: 'needs-q3', + orderedItemIds: ['water', 'food', 'unknown', 'shoes', 'toy', 'crown'], + }, + ]) + assert.fail('expected unknown sort item') + } catch (error) { + assert.ok(error instanceof LessonGradingError) + assert.equal(error.statusCode, 400) + } + + try { + gradeLessonSubmission(needsAndWantsLesson, [ + { + questionId: 'needs-q3', + orderedItemIds: ['water', 'water', 'medicine', 'shoes', 'toy', 'crown'], + }, + ]) + assert.fail('expected duplicate sort item') + } catch (error) { + assert.ok(error instanceof LessonGradingError) + assert.equal(error.statusCode, 400) + } + + assert.equal(gradeLessonSubmission(inflationLesson, THREE_CORRECT_INFLATION).score, 60) + assert.equal(gradeLessonSubmission(inflationLesson, FOUR_CORRECT_INFLATION).score, 80) + assert.equal(gradeLessonSubmission(inflationLesson, ALL_CORRECT_INFLATION).score, 100) + + assert.equal(starsFromScore(59), 0) + assert.equal(starsFromScore(60), 1) + assert.equal(starsFromScore(74), 1) + assert.equal(starsFromScore(75), 2) + assert.equal(starsFromScore(89), 2) + assert.equal(starsFromScore(90), 3) + + try { + progression.completeLevel(TEST_USER, 6, { answers: ONE_CORRECT_INFLATION }) + assert.fail('expected fail below 60') + } catch (error) { + assert.ok(error instanceof ProgressionError) + assert.equal(error.statusCode, 400) + } + + progression.startLevel(TEST_USER, 6) + const first = progression.completeLevel(TEST_USER, 6, { + answers: THREE_CORRECT_INFLATION, + durationMs: 84000, + }) + assert.equal(first.stars, 1) + assert.equal(first.score, 60) + assert.ok(first.isFirstCompletion) + assert.ok(first.bestScoreImproved) + assert.ok(rewards.hasReward(TEST_USER, 'level:6:first-completion')) + + const duplicate = progression.completeLevel(TEST_USER, 6, { answers: THREE_CORRECT_INFLATION }) + assert.equal(duplicate.isFirstCompletion, false) + assert.equal(duplicate.reward.xp, 0) + assert.equal(duplicate.reward.coins, 0) + + try { + progression.completeLevel(TEST_USER, 6, { answers: ONE_CORRECT_INFLATION }) + assert.fail('expected low score rejection') + } catch (error) { + assert.ok(error instanceof ProgressionError) + } + assert.equal(progression.buildLevelItems(TEST_USER).find((l) => l.id === 6)!.stars, 1) + + const improved = progression.completeLevel(TEST_USER, 6, { answers: ALL_CORRECT_INFLATION }) + assert.equal(improved.stars, 3) + assert.ok(improved.bestScoreImproved) + assert.ok(improved.reward.xp > 0) + assert.ok(rewards.hasReward(TEST_USER, 'level:6:star:3')) + + const replayLower = progression.completeLevel(TEST_USER, 6, { answers: FOUR_CORRECT_INFLATION }) + assert.equal(replayLower.stars, 3) + assert.equal(replayLower.reward.xp, 0) + + const detail = progression.getGoldLandLevel(TEST_USER, 6) + const json = JSON.stringify(detail) + assert.ok(!json.includes('correctAnswerId')) + assert.ok(!json.includes('correctOrder')) + assert.ok(!json.includes('answerKey')) + const sanitized = sanitizeLesson(loadLessonForLevel('inflation')!) + for (const step of sanitized.steps) { + assert.ok(!('correctAnswerId' in step)) + assert.ok(!('correctOrder' in step)) + } + + const moneyLesson = loadLessonForLevel('what-is-money')! + const moneyAnswers = buildAllCorrect(moneyLesson) + try { + progression.completeLevel(TEST_USER, 6, { answers: moneyAnswers }) + assert.fail('expected wrong lesson answers for level 6') + } catch (error) { + assert.ok(error instanceof ProgressionError) + assert.equal(error.statusCode, 400) + } + + resetTestUser(database) + progression.startLevel(TEST_USER, 1) + const level1Answers = buildAllCorrect(loadLessonForLevel('what-is-money')!) + const level1Result = progression.completeLevel(TEST_USER, 1, { answers: level1Answers }) + assert.equal(level1Result.stars, 3) + assert.equal(level1Result.unlockedLevelId, 2) + assert.equal(progression.buildLevelItems(TEST_USER).find((l) => l.id === 2)!.status, 'available') + + const level1Again = progression.completeLevel(TEST_USER, 1, { answers: level1Answers }) + assert.equal(level1Again.isFirstCompletion, false) + assert.equal(level1Again.reward.xp, 0) + + console.log('progression tests passed') + } finally { + await app.close() + try { + rmSync(testDir, { recursive: true, force: true }) + } catch { + // ignore + } + } +} + +runTests().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..2fe1df2 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/tsconfig.build.tsbuildinfo b/tsconfig.build.tsbuildinfo new file mode 100644 index 0000000..bbb5835 --- /dev/null +++ b/tsconfig.build.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.dom.asynciterable.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/typescript/lib/lib.es2022.full.d.ts","./node_modules/reflect-metadata/index.d.ts","./node_modules/@nestjs/common/decorators/core/bind.decorator.d.ts","./node_modules/@nestjs/common/interfaces/abstract.interface.d.ts","./node_modules/@nestjs/common/interfaces/controllers/controller-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/controllers/controller.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/arguments-host.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/exception-filter.interface.d.ts","./node_modules/rxjs/dist/types/internal/subscription.d.ts","./node_modules/rxjs/dist/types/internal/subscriber.d.ts","./node_modules/rxjs/dist/types/internal/operator.d.ts","./node_modules/rxjs/dist/types/internal/observable.d.ts","./node_modules/rxjs/dist/types/internal/types.d.ts","./node_modules/rxjs/dist/types/internal/operators/audit.d.ts","./node_modules/rxjs/dist/types/internal/operators/audittime.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffer.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffercount.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffertime.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffertoggle.d.ts","./node_modules/rxjs/dist/types/internal/operators/bufferwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/catcherror.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatestall.d.ts","./node_modules/rxjs/dist/types/internal/operators/combineall.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatest.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatestwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/concat.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatall.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatmapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/connect.d.ts","./node_modules/rxjs/dist/types/internal/operators/count.d.ts","./node_modules/rxjs/dist/types/internal/operators/debounce.d.ts","./node_modules/rxjs/dist/types/internal/operators/debouncetime.d.ts","./node_modules/rxjs/dist/types/internal/operators/defaultifempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/delay.d.ts","./node_modules/rxjs/dist/types/internal/operators/delaywhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/dematerialize.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinct.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinctuntilchanged.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinctuntilkeychanged.d.ts","./node_modules/rxjs/dist/types/internal/operators/elementat.d.ts","./node_modules/rxjs/dist/types/internal/operators/endwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/every.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaustall.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaust.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaustmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/expand.d.ts","./node_modules/rxjs/dist/types/internal/operators/filter.d.ts","./node_modules/rxjs/dist/types/internal/operators/finalize.d.ts","./node_modules/rxjs/dist/types/internal/operators/find.d.ts","./node_modules/rxjs/dist/types/internal/operators/findindex.d.ts","./node_modules/rxjs/dist/types/internal/operators/first.d.ts","./node_modules/rxjs/dist/types/internal/subject.d.ts","./node_modules/rxjs/dist/types/internal/operators/groupby.d.ts","./node_modules/rxjs/dist/types/internal/operators/ignoreelements.d.ts","./node_modules/rxjs/dist/types/internal/operators/isempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/last.d.ts","./node_modules/rxjs/dist/types/internal/operators/map.d.ts","./node_modules/rxjs/dist/types/internal/operators/mapto.d.ts","./node_modules/rxjs/dist/types/internal/notification.d.ts","./node_modules/rxjs/dist/types/internal/operators/materialize.d.ts","./node_modules/rxjs/dist/types/internal/operators/max.d.ts","./node_modules/rxjs/dist/types/internal/operators/merge.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergeall.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergemap.d.ts","./node_modules/rxjs/dist/types/internal/operators/flatmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergemapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergescan.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergewith.d.ts","./node_modules/rxjs/dist/types/internal/operators/min.d.ts","./node_modules/rxjs/dist/types/internal/observable/connectableobservable.d.ts","./node_modules/rxjs/dist/types/internal/operators/multicast.d.ts","./node_modules/rxjs/dist/types/internal/operators/observeon.d.ts","./node_modules/rxjs/dist/types/internal/operators/onerrorresumenextwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/pairwise.d.ts","./node_modules/rxjs/dist/types/internal/operators/partition.d.ts","./node_modules/rxjs/dist/types/internal/operators/pluck.d.ts","./node_modules/rxjs/dist/types/internal/operators/publish.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishbehavior.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishlast.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishreplay.d.ts","./node_modules/rxjs/dist/types/internal/operators/race.d.ts","./node_modules/rxjs/dist/types/internal/operators/racewith.d.ts","./node_modules/rxjs/dist/types/internal/operators/reduce.d.ts","./node_modules/rxjs/dist/types/internal/operators/repeat.d.ts","./node_modules/rxjs/dist/types/internal/operators/repeatwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/retry.d.ts","./node_modules/rxjs/dist/types/internal/operators/retrywhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/refcount.d.ts","./node_modules/rxjs/dist/types/internal/operators/sample.d.ts","./node_modules/rxjs/dist/types/internal/operators/sampletime.d.ts","./node_modules/rxjs/dist/types/internal/operators/scan.d.ts","./node_modules/rxjs/dist/types/internal/operators/sequenceequal.d.ts","./node_modules/rxjs/dist/types/internal/operators/share.d.ts","./node_modules/rxjs/dist/types/internal/operators/sharereplay.d.ts","./node_modules/rxjs/dist/types/internal/operators/single.d.ts","./node_modules/rxjs/dist/types/internal/operators/skip.d.ts","./node_modules/rxjs/dist/types/internal/operators/skiplast.d.ts","./node_modules/rxjs/dist/types/internal/operators/skipuntil.d.ts","./node_modules/rxjs/dist/types/internal/operators/skipwhile.d.ts","./node_modules/rxjs/dist/types/internal/operators/startwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/subscribeon.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchall.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchmapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchscan.d.ts","./node_modules/rxjs/dist/types/internal/operators/take.d.ts","./node_modules/rxjs/dist/types/internal/operators/takelast.d.ts","./node_modules/rxjs/dist/types/internal/operators/takeuntil.d.ts","./node_modules/rxjs/dist/types/internal/operators/takewhile.d.ts","./node_modules/rxjs/dist/types/internal/operators/tap.d.ts","./node_modules/rxjs/dist/types/internal/operators/throttle.d.ts","./node_modules/rxjs/dist/types/internal/operators/throttletime.d.ts","./node_modules/rxjs/dist/types/internal/operators/throwifempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeinterval.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeout.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeoutwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/timestamp.d.ts","./node_modules/rxjs/dist/types/internal/operators/toarray.d.ts","./node_modules/rxjs/dist/types/internal/operators/window.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowcount.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowtime.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowtoggle.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/withlatestfrom.d.ts","./node_modules/rxjs/dist/types/internal/operators/zip.d.ts","./node_modules/rxjs/dist/types/internal/operators/zipall.d.ts","./node_modules/rxjs/dist/types/internal/operators/zipwith.d.ts","./node_modules/rxjs/dist/types/operators/index.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/action.d.ts","./node_modules/rxjs/dist/types/internal/scheduler.d.ts","./node_modules/rxjs/dist/types/internal/testing/testmessage.d.ts","./node_modules/rxjs/dist/types/internal/testing/subscriptionlog.d.ts","./node_modules/rxjs/dist/types/internal/testing/subscriptionloggable.d.ts","./node_modules/rxjs/dist/types/internal/testing/coldobservable.d.ts","./node_modules/rxjs/dist/types/internal/testing/hotobservable.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asyncscheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/timerhandle.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asyncaction.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/virtualtimescheduler.d.ts","./node_modules/rxjs/dist/types/internal/testing/testscheduler.d.ts","./node_modules/rxjs/dist/types/testing/index.d.ts","./node_modules/rxjs/dist/types/internal/symbol/observable.d.ts","./node_modules/rxjs/dist/types/internal/observable/dom/animationframes.d.ts","./node_modules/rxjs/dist/types/internal/behaviorsubject.d.ts","./node_modules/rxjs/dist/types/internal/replaysubject.d.ts","./node_modules/rxjs/dist/types/internal/asyncsubject.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asapscheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asap.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/async.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/queuescheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/queue.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/animationframescheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/animationframe.d.ts","./node_modules/rxjs/dist/types/internal/util/identity.d.ts","./node_modules/rxjs/dist/types/internal/util/pipe.d.ts","./node_modules/rxjs/dist/types/internal/util/noop.d.ts","./node_modules/rxjs/dist/types/internal/util/isobservable.d.ts","./node_modules/rxjs/dist/types/internal/lastvaluefrom.d.ts","./node_modules/rxjs/dist/types/internal/firstvaluefrom.d.ts","./node_modules/rxjs/dist/types/internal/util/argumentoutofrangeerror.d.ts","./node_modules/rxjs/dist/types/internal/util/emptyerror.d.ts","./node_modules/rxjs/dist/types/internal/util/notfounderror.d.ts","./node_modules/rxjs/dist/types/internal/util/objectunsubscribederror.d.ts","./node_modules/rxjs/dist/types/internal/util/sequenceerror.d.ts","./node_modules/rxjs/dist/types/internal/util/unsubscriptionerror.d.ts","./node_modules/rxjs/dist/types/internal/observable/bindcallback.d.ts","./node_modules/rxjs/dist/types/internal/observable/bindnodecallback.d.ts","./node_modules/rxjs/dist/types/internal/anycatcher.d.ts","./node_modules/rxjs/dist/types/internal/observable/combinelatest.d.ts","./node_modules/rxjs/dist/types/internal/observable/concat.d.ts","./node_modules/rxjs/dist/types/internal/observable/connectable.d.ts","./node_modules/rxjs/dist/types/internal/observable/defer.d.ts","./node_modules/rxjs/dist/types/internal/observable/empty.d.ts","./node_modules/rxjs/dist/types/internal/observable/forkjoin.d.ts","./node_modules/rxjs/dist/types/internal/observable/from.d.ts","./node_modules/rxjs/dist/types/internal/observable/fromevent.d.ts","./node_modules/rxjs/dist/types/internal/observable/fromeventpattern.d.ts","./node_modules/rxjs/dist/types/internal/observable/generate.d.ts","./node_modules/rxjs/dist/types/internal/observable/iif.d.ts","./node_modules/rxjs/dist/types/internal/observable/interval.d.ts","./node_modules/rxjs/dist/types/internal/observable/merge.d.ts","./node_modules/rxjs/dist/types/internal/observable/never.d.ts","./node_modules/rxjs/dist/types/internal/observable/of.d.ts","./node_modules/rxjs/dist/types/internal/observable/onerrorresumenext.d.ts","./node_modules/rxjs/dist/types/internal/observable/pairs.d.ts","./node_modules/rxjs/dist/types/internal/observable/partition.d.ts","./node_modules/rxjs/dist/types/internal/observable/race.d.ts","./node_modules/rxjs/dist/types/internal/observable/range.d.ts","./node_modules/rxjs/dist/types/internal/observable/throwerror.d.ts","./node_modules/rxjs/dist/types/internal/observable/timer.d.ts","./node_modules/rxjs/dist/types/internal/observable/using.d.ts","./node_modules/rxjs/dist/types/internal/observable/zip.d.ts","./node_modules/rxjs/dist/types/internal/scheduled/scheduled.d.ts","./node_modules/rxjs/dist/types/internal/config.d.ts","./node_modules/rxjs/dist/types/index.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/ws-exception-filter.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validation-error.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/execution-context.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/can-activate.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/custom-route-param-factory.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/nest-interceptor.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/paramtype.interface.d.ts","./node_modules/@nestjs/common/interfaces/type.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/pipe-transform.interface.d.ts","./node_modules/@nestjs/common/enums/request-method.enum.d.ts","./node_modules/@nestjs/common/enums/http-status.enum.d.ts","./node_modules/@nestjs/common/enums/shutdown-signal.enum.d.ts","./node_modules/@nestjs/common/enums/version-type.enum.d.ts","./node_modules/@nestjs/common/enums/index.d.ts","./node_modules/@nestjs/common/interfaces/version-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-configuration.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-consumer.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-config-proxy.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/nest-middleware.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/index.d.ts","./node_modules/@nestjs/common/interfaces/global-prefix-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/before-application-shutdown.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-application-bootstrap.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-application-shutdown.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-destroy.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-init.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/index.d.ts","./node_modules/@nestjs/common/interfaces/http/http-exception-body.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/http-redirect-response.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/cors-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/https-options.interface.d.ts","./node_modules/@nestjs/common/services/logger.service.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-context-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/http-server.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/message-event.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/raw-body-request.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/index.d.ts","./node_modules/@nestjs/common/interfaces/injectable.interface.d.ts","./node_modules/@nestjs/common/interfaces/microservices/nest-hybrid-application-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/forward-reference.interface.d.ts","./node_modules/@nestjs/common/interfaces/scope-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/injection-token.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/optional-factory-dependency.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/provider.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/module-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/dynamic-module.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/introspection-result.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/nest-module.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/index.d.ts","./node_modules/@nestjs/common/interfaces/shutdown-hooks-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-context.interface.d.ts","./node_modules/@nestjs/common/interfaces/websockets/web-socket-adapter.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-application.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-microservice.interface.d.ts","./node_modules/@nestjs/common/interfaces/index.d.ts","./node_modules/@nestjs/common/decorators/core/catch.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/controller.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/dependencies.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/exception-filters.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/inject.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/injectable.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/optional.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/set-metadata.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-guards.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-interceptors.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-pipes.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/apply-decorators.d.ts","./node_modules/@nestjs/common/decorators/core/version.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/index.d.ts","./node_modules/@nestjs/common/decorators/modules/global.decorator.d.ts","./node_modules/@nestjs/common/decorators/modules/module.decorator.d.ts","./node_modules/@nestjs/common/decorators/modules/index.d.ts","./node_modules/@nestjs/common/decorators/http/request-mapping.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/route-params.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/http-code.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/create-route-param-metadata.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/render.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/header.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/redirect.decorator.d.ts","./node_modules/@nestjs/common/constants.d.ts","./node_modules/@nestjs/common/decorators/http/sse.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/index.d.ts","./node_modules/@nestjs/common/decorators/index.d.ts","./node_modules/@nestjs/common/exceptions/intrinsic.exception.d.ts","./node_modules/@nestjs/common/exceptions/http.exception.d.ts","./node_modules/@nestjs/common/exceptions/bad-gateway.exception.d.ts","./node_modules/@nestjs/common/exceptions/bad-request.exception.d.ts","./node_modules/@nestjs/common/exceptions/conflict.exception.d.ts","./node_modules/@nestjs/common/exceptions/forbidden.exception.d.ts","./node_modules/@nestjs/common/exceptions/gateway-timeout.exception.d.ts","./node_modules/@nestjs/common/exceptions/gone.exception.d.ts","./node_modules/@nestjs/common/exceptions/http-version-not-supported.exception.d.ts","./node_modules/@nestjs/common/exceptions/im-a-teapot.exception.d.ts","./node_modules/@nestjs/common/exceptions/internal-server-error.exception.d.ts","./node_modules/@nestjs/common/exceptions/method-not-allowed.exception.d.ts","./node_modules/@nestjs/common/exceptions/misdirected.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-acceptable.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-found.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-implemented.exception.d.ts","./node_modules/@nestjs/common/exceptions/payload-too-large.exception.d.ts","./node_modules/@nestjs/common/exceptions/precondition-failed.exception.d.ts","./node_modules/@nestjs/common/exceptions/request-timeout.exception.d.ts","./node_modules/@nestjs/common/exceptions/service-unavailable.exception.d.ts","./node_modules/@nestjs/common/exceptions/unauthorized.exception.d.ts","./node_modules/@nestjs/common/exceptions/unprocessable-entity.exception.d.ts","./node_modules/@nestjs/common/exceptions/unsupported-media-type.exception.d.ts","./node_modules/@nestjs/common/exceptions/index.d.ts","./node_modules/@nestjs/common/services/console-logger.service.d.ts","./node_modules/@nestjs/common/services/utils/filter-log-levels.util.d.ts","./node_modules/@nestjs/common/services/index.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/streamable-options.interface.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/streamable-handler-response.interface.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/index.d.ts","./node_modules/@nestjs/common/file-stream/streamable-file.d.ts","./node_modules/@nestjs/common/file-stream/index.d.ts","./node_modules/@nestjs/common/module-utils/constants.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-async-options.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-cls.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-host.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/index.d.ts","./node_modules/@nestjs/common/module-utils/configurable-module.builder.d.ts","./node_modules/@nestjs/common/module-utils/index.d.ts","./node_modules/@nestjs/common/pipes/default-value.pipe.d.ts","./node_modules/@nestjs/common/pipes/file/interfaces/file.interface.d.ts","./node_modules/@nestjs/common/pipes/file/interfaces/index.d.ts","./node_modules/@nestjs/common/pipes/file/file-validator-context.interface.d.ts","./node_modules/@nestjs/common/pipes/file/file-validator.interface.d.ts","./node_modules/@nestjs/common/pipes/file/file-type.validator.d.ts","./node_modules/@nestjs/common/pipes/file/max-file-size.validator.d.ts","./node_modules/@nestjs/common/utils/http-error-by-code.util.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file-options.interface.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file.pipe.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file-pipe.builder.d.ts","./node_modules/@nestjs/common/pipes/file/index.d.ts","./node_modules/@nestjs/common/interfaces/external/class-transform-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/transformer-package.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validator-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validator-package.interface.d.ts","./node_modules/@nestjs/common/pipes/validation.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-array.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-bool.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-date.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-enum.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-float.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-int.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-uuid.pipe.d.ts","./node_modules/@nestjs/common/pipes/index.d.ts","./node_modules/@nestjs/common/serializer/class-serializer.interfaces.d.ts","./node_modules/@nestjs/common/serializer/class-serializer.interceptor.d.ts","./node_modules/@nestjs/common/serializer/decorators/serialize-options.decorator.d.ts","./node_modules/@nestjs/common/serializer/decorators/index.d.ts","./node_modules/@nestjs/common/serializer/index.d.ts","./node_modules/@nestjs/common/utils/forward-ref.util.d.ts","./node_modules/@nestjs/common/utils/index.d.ts","./node_modules/@nestjs/common/index.d.ts","./node_modules/@nestjs/config/dist/conditional.module.d.ts","./node_modules/@nestjs/config/dist/interfaces/config-change-event.interface.d.ts","./node_modules/@nestjs/config/dist/types/config-object.type.d.ts","./node_modules/@nestjs/config/dist/types/config.type.d.ts","./node_modules/@nestjs/config/dist/types/no-infer.type.d.ts","./node_modules/@nestjs/config/dist/types/path-value.type.d.ts","./node_modules/@nestjs/config/dist/types/index.d.ts","./node_modules/@nestjs/config/dist/interfaces/config-factory.interface.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/round-robin-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/buffer/index.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/dotenv-expand/lib/main.d.ts","./node_modules/@nestjs/config/dist/interfaces/config-module-options.interface.d.ts","./node_modules/@nestjs/config/dist/interfaces/index.d.ts","./node_modules/@nestjs/config/dist/config.module.d.ts","./node_modules/@nestjs/config/dist/config.service.d.ts","./node_modules/@nestjs/config/dist/utils/register-as.util.d.ts","./node_modules/@nestjs/config/dist/utils/get-config-token.util.d.ts","./node_modules/@nestjs/config/dist/utils/index.d.ts","./node_modules/@nestjs/config/dist/index.d.ts","./node_modules/@nestjs/config/index.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/expose-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/exclude-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/transform-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/type-discriminator-descriptor.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/type-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/exclude-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/expose-metadata.interface.d.ts","./node_modules/class-transformer/types/enums/transformation-type.enum.d.ts","./node_modules/class-transformer/types/enums/index.d.ts","./node_modules/class-transformer/types/interfaces/target-map.interface.d.ts","./node_modules/class-transformer/types/interfaces/class-transformer-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/transform-fn-params.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/transform-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/type-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/class-constructor.type.d.ts","./node_modules/class-transformer/types/interfaces/type-help-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/index.d.ts","./node_modules/class-transformer/types/classtransformer.d.ts","./node_modules/class-transformer/types/decorators/exclude.decorator.d.ts","./node_modules/class-transformer/types/decorators/expose.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-instance-to-instance.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-instance-to-plain.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-plain-to-instance.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform.decorator.d.ts","./node_modules/class-transformer/types/decorators/type.decorator.d.ts","./node_modules/class-transformer/types/decorators/index.d.ts","./node_modules/class-transformer/types/index.d.ts","./node_modules/class-validator/types/validation/validationerror.d.ts","./node_modules/class-validator/types/validation/validatoroptions.d.ts","./node_modules/class-validator/types/validation-schema/validationschema.d.ts","./node_modules/class-validator/types/container.d.ts","./node_modules/class-validator/types/validation/validationarguments.d.ts","./node_modules/class-validator/types/decorator/validationoptions.d.ts","./node_modules/class-validator/types/decorator/common/allow.d.ts","./node_modules/class-validator/types/decorator/common/isdefined.d.ts","./node_modules/class-validator/types/decorator/common/isoptional.d.ts","./node_modules/class-validator/types/decorator/common/validate.d.ts","./node_modules/class-validator/types/validation/validatorconstraintinterface.d.ts","./node_modules/class-validator/types/decorator/common/validateby.d.ts","./node_modules/class-validator/types/decorator/common/validateif.d.ts","./node_modules/class-validator/types/decorator/common/validatenested.d.ts","./node_modules/class-validator/types/decorator/common/validatepromise.d.ts","./node_modules/class-validator/types/decorator/common/islatlong.d.ts","./node_modules/class-validator/types/decorator/common/islatitude.d.ts","./node_modules/class-validator/types/decorator/common/islongitude.d.ts","./node_modules/class-validator/types/decorator/common/equals.d.ts","./node_modules/class-validator/types/decorator/common/notequals.d.ts","./node_modules/class-validator/types/decorator/common/isempty.d.ts","./node_modules/class-validator/types/decorator/common/isnotempty.d.ts","./node_modules/class-validator/types/decorator/common/isin.d.ts","./node_modules/class-validator/types/decorator/common/isnotin.d.ts","./node_modules/class-validator/types/decorator/number/isdivisibleby.d.ts","./node_modules/class-validator/types/decorator/number/ispositive.d.ts","./node_modules/class-validator/types/decorator/number/isnegative.d.ts","./node_modules/class-validator/types/decorator/number/max.d.ts","./node_modules/class-validator/types/decorator/number/min.d.ts","./node_modules/class-validator/types/decorator/date/mindate.d.ts","./node_modules/class-validator/types/decorator/date/maxdate.d.ts","./node_modules/class-validator/types/decorator/string/contains.d.ts","./node_modules/class-validator/types/decorator/string/notcontains.d.ts","./node_modules/@types/validator/lib/isboolean.d.ts","./node_modules/@types/validator/lib/isemail.d.ts","./node_modules/@types/validator/lib/isfqdn.d.ts","./node_modules/@types/validator/lib/isiban.d.ts","./node_modules/@types/validator/lib/isiso31661alpha2.d.ts","./node_modules/@types/validator/lib/isiso4217.d.ts","./node_modules/@types/validator/lib/isiso6391.d.ts","./node_modules/@types/validator/lib/istaxid.d.ts","./node_modules/@types/validator/lib/isurl.d.ts","./node_modules/@types/validator/index.d.ts","./node_modules/class-validator/types/decorator/string/isalpha.d.ts","./node_modules/class-validator/types/decorator/string/isalphanumeric.d.ts","./node_modules/class-validator/types/decorator/string/isdecimal.d.ts","./node_modules/class-validator/types/decorator/string/isascii.d.ts","./node_modules/class-validator/types/decorator/string/isbase64.d.ts","./node_modules/class-validator/types/decorator/string/isbytelength.d.ts","./node_modules/class-validator/types/decorator/string/iscreditcard.d.ts","./node_modules/class-validator/types/decorator/string/iscurrency.d.ts","./node_modules/class-validator/types/decorator/string/isemail.d.ts","./node_modules/class-validator/types/decorator/string/isfqdn.d.ts","./node_modules/class-validator/types/decorator/string/isfullwidth.d.ts","./node_modules/class-validator/types/decorator/string/ishalfwidth.d.ts","./node_modules/class-validator/types/decorator/string/isvariablewidth.d.ts","./node_modules/class-validator/types/decorator/string/ishexcolor.d.ts","./node_modules/class-validator/types/decorator/string/ishexadecimal.d.ts","./node_modules/class-validator/types/decorator/string/ismacaddress.d.ts","./node_modules/class-validator/types/decorator/string/isip.d.ts","./node_modules/class-validator/types/decorator/string/isport.d.ts","./node_modules/class-validator/types/decorator/string/isisbn.d.ts","./node_modules/class-validator/types/decorator/string/isisin.d.ts","./node_modules/class-validator/types/decorator/string/isiso8601.d.ts","./node_modules/class-validator/types/decorator/string/isjson.d.ts","./node_modules/class-validator/types/decorator/string/isjwt.d.ts","./node_modules/class-validator/types/decorator/string/islowercase.d.ts","./node_modules/class-validator/types/decorator/string/ismobilephone.d.ts","./node_modules/class-validator/types/decorator/string/isiso31661alpha2.d.ts","./node_modules/class-validator/types/decorator/string/isiso31661alpha3.d.ts","./node_modules/class-validator/types/decorator/string/ismongoid.d.ts","./node_modules/class-validator/types/decorator/string/ismultibyte.d.ts","./node_modules/class-validator/types/decorator/string/issurrogatepair.d.ts","./node_modules/class-validator/types/decorator/string/isurl.d.ts","./node_modules/class-validator/types/decorator/string/isuuid.d.ts","./node_modules/class-validator/types/decorator/string/isfirebasepushid.d.ts","./node_modules/class-validator/types/decorator/string/isuppercase.d.ts","./node_modules/class-validator/types/decorator/string/length.d.ts","./node_modules/class-validator/types/decorator/string/maxlength.d.ts","./node_modules/class-validator/types/decorator/string/minlength.d.ts","./node_modules/class-validator/types/decorator/string/matches.d.ts","./node_modules/libphonenumber-js/types.d.cts","./node_modules/libphonenumber-js/max/index.d.cts","./node_modules/class-validator/types/decorator/string/isphonenumber.d.ts","./node_modules/class-validator/types/decorator/string/ismilitarytime.d.ts","./node_modules/class-validator/types/decorator/string/ishash.d.ts","./node_modules/class-validator/types/decorator/string/isissn.d.ts","./node_modules/class-validator/types/decorator/string/isdatestring.d.ts","./node_modules/class-validator/types/decorator/string/isbooleanstring.d.ts","./node_modules/class-validator/types/decorator/string/isnumberstring.d.ts","./node_modules/class-validator/types/decorator/string/isbase32.d.ts","./node_modules/class-validator/types/decorator/string/isbic.d.ts","./node_modules/class-validator/types/decorator/string/isbtcaddress.d.ts","./node_modules/class-validator/types/decorator/string/isdatauri.d.ts","./node_modules/class-validator/types/decorator/string/isean.d.ts","./node_modules/class-validator/types/decorator/string/isethereumaddress.d.ts","./node_modules/class-validator/types/decorator/string/ishsl.d.ts","./node_modules/class-validator/types/decorator/string/isiban.d.ts","./node_modules/class-validator/types/decorator/string/isidentitycard.d.ts","./node_modules/class-validator/types/decorator/string/isisrc.d.ts","./node_modules/class-validator/types/decorator/string/islocale.d.ts","./node_modules/class-validator/types/decorator/string/ismagneturi.d.ts","./node_modules/class-validator/types/decorator/string/ismimetype.d.ts","./node_modules/class-validator/types/decorator/string/isoctal.d.ts","./node_modules/class-validator/types/decorator/string/ispassportnumber.d.ts","./node_modules/class-validator/types/decorator/string/ispostalcode.d.ts","./node_modules/class-validator/types/decorator/string/isrfc3339.d.ts","./node_modules/class-validator/types/decorator/string/isrgbcolor.d.ts","./node_modules/class-validator/types/decorator/string/issemver.d.ts","./node_modules/class-validator/types/decorator/string/isstrongpassword.d.ts","./node_modules/class-validator/types/decorator/string/istimezone.d.ts","./node_modules/class-validator/types/decorator/string/isbase58.d.ts","./node_modules/class-validator/types/decorator/string/is-tax-id.d.ts","./node_modules/class-validator/types/decorator/string/is-iso4217-currency-code.d.ts","./node_modules/class-validator/types/decorator/typechecker/isboolean.d.ts","./node_modules/class-validator/types/decorator/typechecker/isdate.d.ts","./node_modules/class-validator/types/decorator/typechecker/isnumber.d.ts","./node_modules/class-validator/types/decorator/typechecker/isenum.d.ts","./node_modules/class-validator/types/decorator/typechecker/isint.d.ts","./node_modules/class-validator/types/decorator/typechecker/isstring.d.ts","./node_modules/class-validator/types/decorator/typechecker/isarray.d.ts","./node_modules/class-validator/types/decorator/typechecker/isobject.d.ts","./node_modules/class-validator/types/decorator/array/arraycontains.d.ts","./node_modules/class-validator/types/decorator/array/arraynotcontains.d.ts","./node_modules/class-validator/types/decorator/array/arraynotempty.d.ts","./node_modules/class-validator/types/decorator/array/arrayminsize.d.ts","./node_modules/class-validator/types/decorator/array/arraymaxsize.d.ts","./node_modules/class-validator/types/decorator/array/arrayunique.d.ts","./node_modules/class-validator/types/decorator/object/isnotemptyobject.d.ts","./node_modules/class-validator/types/decorator/object/isinstance.d.ts","./node_modules/class-validator/types/decorator/decorators.d.ts","./node_modules/class-validator/types/validation/validationtypes.d.ts","./node_modules/class-validator/types/validation/validator.d.ts","./node_modules/class-validator/types/register-decorator.d.ts","./node_modules/class-validator/types/metadata/validationmetadataargs.d.ts","./node_modules/class-validator/types/metadata/validationmetadata.d.ts","./node_modules/class-validator/types/metadata/constraintmetadata.d.ts","./node_modules/class-validator/types/metadata/metadatastorage.d.ts","./node_modules/class-validator/types/index.d.ts","./src/config/env.validation.ts","./node_modules/@types/better-sqlite3/index.d.ts","./src/database/migration.service.ts","./src/database/database.service.ts","./src/database/database.module.ts","./src/common/decorators/public.decorator.ts","./src/health/health.controller.ts","./src/health/health.module.ts","./src/shared/catalog/achievements.constants.ts","./src/shared/types/user.types.ts","./src/shared/catalog/default-user-state.ts","./src/gold-land/exceptions/progression.exception.ts","./src/gold-land/levels/levels.ts","./src/gold-land/lessons/lesson.types.ts","./src/gold-land/lessons/lesson.sanitizer.ts","./src/gold-land/levels/level.types.ts","./src/gold-land/lessons/inflation.lesson.ts","./src/gold-land/lessons/what-is-money.lesson.ts","./src/gold-land/lessons/saving.lesson.ts","./src/gold-land/lessons/needs-and-wants.lesson.ts","./src/gold-land/lessons/what-is-gold.lesson.ts","./src/gold-land/lessons/gold-market.lesson.ts","./src/gold-land/lessons/lesson.grader.ts","./src/gold-land/lessons/lesson.registry.ts","./src/gold-land/lessons/lesson.loader.ts","./src/shared/catalog/market.constants.ts","./src/shared/economy/user-economy.helpers.ts","./src/gold-land/services/reward.service.ts","./src/gold-land/services/progression.service.ts","./src/users/users.service.ts","./node_modules/fast-uri/types/index.d.ts","./node_modules/ajv/dist/compile/codegen/code.d.ts","./node_modules/ajv/dist/compile/codegen/scope.d.ts","./node_modules/ajv/dist/compile/codegen/index.d.ts","./node_modules/ajv/dist/compile/rules.d.ts","./node_modules/ajv/dist/compile/util.d.ts","./node_modules/ajv/dist/compile/validate/subschema.d.ts","./node_modules/ajv/dist/compile/errors.d.ts","./node_modules/ajv/dist/compile/validate/index.d.ts","./node_modules/ajv/dist/compile/validate/datatype.d.ts","./node_modules/ajv/dist/vocabularies/applicator/additionalitems.d.ts","./node_modules/ajv/dist/vocabularies/applicator/items2020.d.ts","./node_modules/ajv/dist/vocabularies/applicator/contains.d.ts","./node_modules/ajv/dist/vocabularies/applicator/dependencies.d.ts","./node_modules/ajv/dist/vocabularies/applicator/propertynames.d.ts","./node_modules/ajv/dist/vocabularies/applicator/additionalproperties.d.ts","./node_modules/ajv/dist/vocabularies/applicator/not.d.ts","./node_modules/ajv/dist/vocabularies/applicator/anyof.d.ts","./node_modules/ajv/dist/vocabularies/applicator/oneof.d.ts","./node_modules/ajv/dist/vocabularies/applicator/if.d.ts","./node_modules/ajv/dist/vocabularies/applicator/index.d.ts","./node_modules/ajv/dist/vocabularies/validation/limitnumber.d.ts","./node_modules/ajv/dist/vocabularies/validation/multipleof.d.ts","./node_modules/ajv/dist/vocabularies/validation/pattern.d.ts","./node_modules/ajv/dist/vocabularies/validation/required.d.ts","./node_modules/ajv/dist/vocabularies/validation/uniqueitems.d.ts","./node_modules/ajv/dist/vocabularies/validation/const.d.ts","./node_modules/ajv/dist/vocabularies/validation/enum.d.ts","./node_modules/ajv/dist/vocabularies/validation/index.d.ts","./node_modules/ajv/dist/vocabularies/format/format.d.ts","./node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedproperties.d.ts","./node_modules/ajv/dist/vocabularies/unevaluated/unevaluateditems.d.ts","./node_modules/ajv/dist/vocabularies/validation/dependentrequired.d.ts","./node_modules/ajv/dist/vocabularies/discriminator/types.d.ts","./node_modules/ajv/dist/vocabularies/discriminator/index.d.ts","./node_modules/ajv/dist/vocabularies/errors.d.ts","./node_modules/ajv/dist/types/json-schema.d.ts","./node_modules/ajv/dist/types/jtd-schema.d.ts","./node_modules/ajv/dist/runtime/validation_error.d.ts","./node_modules/ajv/dist/compile/ref_error.d.ts","./node_modules/ajv/dist/core.d.ts","./node_modules/ajv/dist/compile/resolve.d.ts","./node_modules/ajv/dist/compile/index.d.ts","./node_modules/ajv/dist/types/index.d.ts","./node_modules/ajv/dist/ajv.d.ts","./node_modules/ajv/dist/vocabularies/jtd/error.d.ts","./node_modules/ajv/dist/vocabularies/jtd/type.d.ts","./node_modules/ajv/dist/vocabularies/jtd/enum.d.ts","./node_modules/ajv/dist/vocabularies/jtd/elements.d.ts","./node_modules/ajv/dist/vocabularies/jtd/properties.d.ts","./node_modules/ajv/dist/vocabularies/jtd/discriminator.d.ts","./node_modules/ajv/dist/vocabularies/jtd/values.d.ts","./node_modules/ajv/dist/vocabularies/jtd/index.d.ts","./node_modules/ajv/dist/jtd.d.ts","./node_modules/@fastify/ajv-compiler/types/index.d.ts","./node_modules/@fastify/error/types/index.d.ts","./node_modules/fast-json-stringify/types/index.d.ts","./node_modules/@fastify/fast-json-stringify-compiler/types/index.d.ts","./node_modules/find-my-way/index.d.ts","./node_modules/light-my-request/types/index.d.ts","./node_modules/fastify/types/utils.d.ts","./node_modules/fastify/types/schema.d.ts","./node_modules/fastify/types/type-provider.d.ts","./node_modules/fastify/types/reply.d.ts","./node_modules/pino-std-serializers/index.d.ts","./node_modules/sonic-boom/types/index.d.ts","./node_modules/thread-stream/index.d.ts","./node_modules/pino/pino.d.ts","./node_modules/fastify/types/logger.d.ts","./node_modules/fastify/types/plugin.d.ts","./node_modules/fastify/types/register.d.ts","./node_modules/fastify/types/instance.d.ts","./node_modules/fastify/types/hooks.d.ts","./node_modules/fastify/types/route.d.ts","./node_modules/fastify/types/context.d.ts","./node_modules/fastify/types/request.d.ts","./node_modules/fastify/types/content-type-parser.d.ts","./node_modules/fastify/types/errors.d.ts","./node_modules/fastify/types/server-factory.d.ts","./node_modules/fastify/fastify.d.ts","./src/common/types/authenticated-request.ts","./src/common/decorators/current-user.decorator.ts","./src/common/pipes/strict-body.pipe.ts","./node_modules/@types/ms/index.d.ts","./node_modules/@types/jsonwebtoken/index.d.ts","./node_modules/@nestjs/jwt/dist/interfaces/jwt-module-options.interface.d.ts","./node_modules/@nestjs/jwt/dist/interfaces/index.d.ts","./node_modules/@nestjs/jwt/dist/jwt.errors.d.ts","./node_modules/@nestjs/jwt/dist/jwt.module.d.ts","./node_modules/@nestjs/jwt/dist/jwt.service.d.ts","./node_modules/@nestjs/jwt/dist/index.d.ts","./node_modules/@nestjs/jwt/index.d.ts","./node_modules/bcryptjs/umd/types.d.ts","./node_modules/bcryptjs/umd/index.d.ts","./src/auth/auth.service.ts","./src/users/dto/update-profile.dto.ts","./src/users/dto/update-password.dto.ts","./src/users/users.controller.ts","./node_modules/@nestjs/core/adapters/http-adapter.d.ts","./node_modules/@nestjs/core/adapters/index.d.ts","./node_modules/@nestjs/core/inspector/interfaces/edge.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/entrypoint.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/extras.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/node.interface.d.ts","./node_modules/@nestjs/core/injector/settlement-signal.d.ts","./node_modules/@nestjs/core/injector/injector.d.ts","./node_modules/@nestjs/core/inspector/interfaces/serialized-graph-metadata.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/serialized-graph-json.interface.d.ts","./node_modules/@nestjs/core/inspector/serialized-graph.d.ts","./node_modules/@nestjs/core/injector/opaque-key-factory/interfaces/module-opaque-key-factory.interface.d.ts","./node_modules/@nestjs/core/injector/compiler.d.ts","./node_modules/@nestjs/core/injector/modules-container.d.ts","./node_modules/@nestjs/core/injector/container.d.ts","./node_modules/@nestjs/core/injector/instance-links-host.d.ts","./node_modules/@nestjs/core/injector/abstract-instance-resolver.d.ts","./node_modules/@nestjs/core/injector/module-ref.d.ts","./node_modules/@nestjs/core/injector/module.d.ts","./node_modules/@nestjs/core/injector/instance-wrapper.d.ts","./node_modules/@nestjs/core/router/interfaces/exclude-route-metadata.interface.d.ts","./node_modules/@nestjs/core/application-config.d.ts","./node_modules/@nestjs/core/constants.d.ts","./node_modules/@nestjs/core/discovery/discovery-module.d.ts","./node_modules/@nestjs/core/discovery/discovery-service.d.ts","./node_modules/@nestjs/core/discovery/index.d.ts","./node_modules/@nestjs/core/helpers/http-adapter-host.d.ts","./node_modules/@nestjs/core/exceptions/base-exception-filter.d.ts","./node_modules/@nestjs/core/exceptions/index.d.ts","./node_modules/@nestjs/core/helpers/context-id-factory.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/exception-filter-metadata.interface.d.ts","./node_modules/@nestjs/core/exceptions/exceptions-handler.d.ts","./node_modules/@nestjs/core/router/router-proxy.d.ts","./node_modules/@nestjs/core/helpers/context-creator.d.ts","./node_modules/@nestjs/core/exceptions/base-exception-filter-context.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/index.d.ts","./node_modules/@nestjs/core/exceptions/external-exception-filter.d.ts","./node_modules/@nestjs/core/exceptions/external-exceptions-handler.d.ts","./node_modules/@nestjs/core/exceptions/external-exception-filter-context.d.ts","./node_modules/@nestjs/core/guards/constants.d.ts","./node_modules/@nestjs/core/helpers/execution-context-host.d.ts","./node_modules/@nestjs/core/guards/guards-consumer.d.ts","./node_modules/@nestjs/core/guards/guards-context-creator.d.ts","./node_modules/@nestjs/core/guards/index.d.ts","./node_modules/@nestjs/core/interceptors/interceptors-consumer.d.ts","./node_modules/@nestjs/core/interceptors/interceptors-context-creator.d.ts","./node_modules/@nestjs/core/interceptors/index.d.ts","./node_modules/@nestjs/common/enums/route-paramtypes.enum.d.ts","./node_modules/@nestjs/core/pipes/params-token-factory.d.ts","./node_modules/@nestjs/core/pipes/pipes-consumer.d.ts","./node_modules/@nestjs/core/pipes/pipes-context-creator.d.ts","./node_modules/@nestjs/core/pipes/index.d.ts","./node_modules/@nestjs/core/helpers/context-utils.d.ts","./node_modules/@nestjs/core/injector/inquirer/inquirer-constants.d.ts","./node_modules/@nestjs/core/injector/inquirer/index.d.ts","./node_modules/@nestjs/core/interfaces/module-definition.interface.d.ts","./node_modules/@nestjs/core/interfaces/module-override.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/enhancer-metadata-cache-entry.interface.d.ts","./node_modules/@nestjs/core/inspector/graph-inspector.d.ts","./node_modules/@nestjs/core/metadata-scanner.d.ts","./node_modules/@nestjs/core/scanner.d.ts","./node_modules/@nestjs/core/injector/instance-loader.d.ts","./node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader-options.interface.d.ts","./node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader.d.ts","./node_modules/@nestjs/core/injector/index.d.ts","./node_modules/@nestjs/core/helpers/interfaces/external-handler-metadata.interface.d.ts","./node_modules/@nestjs/core/helpers/interfaces/params-metadata.interface.d.ts","./node_modules/@nestjs/core/helpers/external-context-creator.d.ts","./node_modules/@nestjs/core/helpers/index.d.ts","./node_modules/@nestjs/core/inspector/initialize-on-preview.allowlist.d.ts","./node_modules/@nestjs/core/inspector/partial-graph.host.d.ts","./node_modules/@nestjs/core/inspector/index.d.ts","./node_modules/@nestjs/core/middleware/route-info-path-extractor.d.ts","./node_modules/@nestjs/core/middleware/routes-mapper.d.ts","./node_modules/@nestjs/core/middleware/builder.d.ts","./node_modules/@nestjs/core/middleware/index.d.ts","./node_modules/@nestjs/core/nest-application-context.d.ts","./node_modules/@nestjs/core/nest-application.d.ts","./node_modules/@nestjs/common/interfaces/microservices/nest-microservice-options.interface.d.ts","./node_modules/@nestjs/core/nest-factory.d.ts","./node_modules/@nestjs/core/repl/repl.d.ts","./node_modules/@nestjs/core/repl/index.d.ts","./node_modules/@nestjs/core/router/interfaces/routes.interface.d.ts","./node_modules/@nestjs/core/router/interfaces/index.d.ts","./node_modules/@nestjs/core/router/request/request-constants.d.ts","./node_modules/@nestjs/core/router/request/index.d.ts","./node_modules/@nestjs/core/router/router-module.d.ts","./node_modules/@nestjs/core/router/index.d.ts","./node_modules/@nestjs/core/services/reflector.service.d.ts","./node_modules/@nestjs/core/services/index.d.ts","./node_modules/@nestjs/core/index.d.ts","./src/common/guards/jwt-auth.guard.ts","./src/auth/dto/login.dto.ts","./src/auth/dto/register.dto.ts","./src/auth/auth.controller.ts","./src/auth/auth-seed.service.ts","./src/auth/auth.module.ts","./src/users/users.module.ts","./src/gold-land/dto/complete-level.dto.ts","./src/gold-land/gold-land.controller.ts","./src/gold-land/gold-land.module.ts","./src/market/dto/update-market-preferences.dto.ts","./src/market/market.service.ts","./src/market/market.controller.ts","./src/market/market.module.ts","./src/shared/catalog/challenge.constants.ts","./src/challenges/challenges.service.ts","./src/challenges/dto/complete-daily-challenge.dto.ts","./src/challenges/challenges.controller.ts","./src/challenges/challenges.module.ts","./src/games/dto/complete-game.dto.ts","./src/shared/catalog/games.constants.ts","./src/games/games.service.ts","./src/games/games.controller.ts","./src/games/games.module.ts","./src/store/dto/purchase-store-item.dto.ts","./src/shared/catalog/store.constants.ts","./src/store/store.service.ts","./src/store/store.controller.ts","./src/store/store.module.ts","./src/wallet/dto/buy-gold.dto.ts","./src/wallet/dto/sell-gold.dto.ts","./src/wallet/wallet.service.ts","./src/wallet/wallet.controller.ts","./src/wallet/wallet.module.ts","./src/achievements/achievements.service.ts","./src/achievements/achievements.controller.ts","./src/achievements/achievements.module.ts","./src/app.module.ts","./node_modules/@fastify/cors/types/index.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/external/fastify-static-options.interface.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/external/fastify-view-options.interface.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/external/index.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/nest-fastify-body-parser-options.interface.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/nest-fastify-application.interface.d.ts","./node_modules/@nestjs/platform-fastify/interfaces/index.d.ts","./node_modules/@nestjs/platform-fastify/adapters/fastify-adapter.d.ts","./node_modules/@nestjs/platform-fastify/adapters/index.d.ts","./node_modules/@nestjs/platform-fastify/decorators/route-config.decorator.d.ts","./node_modules/@nestjs/platform-fastify/decorators/route-constraints.decorator.d.ts","./node_modules/@nestjs/platform-fastify/decorators/route-schema.decorator.d.ts","./node_modules/@nestjs/platform-fastify/decorators/index.d.ts","./node_modules/@nestjs/platform-fastify/index.d.ts","./src/common/filters/api-exception.filter.ts","./src/main.ts","./src/config/config.module.ts","./src/database/database.tokens.ts","./src/progression/levels.ts","./src/progression/progression-sync.service.ts","./src/progression/progression.module.ts","./src/wallet/wallet-state.helpers.ts","./node_modules/@types/bcryptjs/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/@types/json-schema/index.d.ts","./node_modules/@types/eslint/use-at-your-own-risk.d.ts","./node_modules/@types/eslint/index.d.ts","./node_modules/@types/eslint-scope/index.d.ts"],"fileIdsList":[[429,484,501,502,782,786,795],[429,484,501,502,535,821],[429,484,501,502],[429,484,501,502,798],[318,429,484,501,502],[418,429,484,501,502],[67,319,320,321,322,323,324,325,326,327,328,329,330,331,429,484,501,502],[270,304,429,484,501,502],[277,429,484,501,502],[267,318,418,429,484,501,502],[336,337,338,339,340,341,342,344,429,484,501,502],[272,429,484,501,502],[318,418,429,484,501,502],[272,343,429,484,501,502],[332,335,345,429,484,501,502],[333,334,429,484,501,502],[308,429,484,501,502],[272,273,274,275,429,484,501,502],[348,429,484,501,502],[290,347,429,484,501,502],[347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,429,484,501,502],[377,429,484,501,502],[374,375,429,484,501,502],[373,376,429,484,501,502,517],[66,276,318,346,370,373,378,385,410,415,417,429,484,501,502],[72,270,429,484,501,502],[71,429,484,501,502],[72,262,263,429,484,501,502,870,875],[262,270,429,484,501,502],[71,261,429,484,501,502],[270,398,429,484,501,502],[264,400,429,484,501,502],[261,265,429,484,501,502],[265,429,484,501,502],[71,318,429,484,501,502],[269,270,429,484,501,502],[282,429,484,501,502],[284,285,286,287,288,429,484,501,502],[276,429,484,501,502],[276,277,296,429,484,501,502],[290,291,297,298,299,429,484,501,502],[68,69,70,71,72,262,263,264,265,266,267,268,269,270,271,277,282,283,289,296,300,301,302,304,312,313,314,315,316,317,429,484,501,502],[295,429,484,501,502],[278,279,280,281,429,484,501,502],[270,278,279,429,484,501,502],[270,276,277,429,484,501,502],[270,280,429,484,501,502],[270,308,429,484,501,502],[303,305,306,307,308,309,310,311,429,484,501,502],[68,270,429,484,501,502],[304,429,484,501,502],[68,270,303,307,309,429,484,501,502],[279,429,484,501,502],[305,429,484,501,502],[270,304,305,306,429,484,501,502],[294,429,484,501,502],[270,274,294,295,312,313,429,484,501,502],[292,293,295,429,484,501,502],[266,268,277,283,297,314,315,318,429,484,501,502],[72,261,266,268,271,314,315,429,484,501,502],[275,429,484,501,502],[261,429,484,501,502],[294,318,379,383,429,484,501,502],[383,384,429,484,501,502],[318,379,429,484,501,502],[318,379,380,429,484,501,502],[380,381,429,484,501,502],[380,381,382,429,484,501,502],[271,429,484,501,502],[388,389,390,429,484,501,502],[388,429,484,501,502],[390,391,392,394,395,396,429,484,501,502],[387,429,484,501,502],[390,393,429,484,501,502],[390,391,392,394,395,429,484,501,502],[271,388,390,394,429,484,501,502],[386,397,402,403,404,405,406,407,408,409,429,484,501,502],[271,318,402,429,484,501,502],[271,393,429,484,501,502],[271,393,418,429,484,501,502],[264,270,271,393,398,399,400,401,429,484,501,502],[261,318,398,399,411,429,484,501,502],[318,398,429,484,501,502],[413,429,484,501,502],[346,411,429,484,501,502],[411,412,414,429,484,501,502],[294,429,484,501,502,529],[294,371,372,429,484,501,502],[303,429,484,501,502],[276,318,429,484,501,502],[416,429,484,501,502],[418,429,484,501,502,538],[261,420,425,429,484,501,502],[419,425,429,484,501,502,538,539,540,543],[425,429,484,501,502],[426,429,484,501,502,536],[420,426,429,484,501,502,537],[421,422,423,424,429,484,501,502],[429,484,501,502,541,542],[425,429,484,501,502,538,544],[429,484,501,502,544],[296,318,418,429,484,501,502],[429,484,501,502,840],[318,418,429,484,501,502,859,860],[343,429,484,501,502],[418,429,484,501,502,853,858,859],[429,484,501,502,863,864],[72,318,429,484,501,502,854,859,873],[418,429,484,501,502,841,866],[71,418,429,484,501,502,867,870],[318,429,484,501,502,854,859,861,872,874,878],[71,429,484,501,502,876,877],[429,484,501,502,867],[261,318,418,429,484,501,502,881],[318,418,429,484,501,502,854,859,861,873],[429,484,501,502,880,882,883],[318,429,484,501,502,859],[429,484,501,502,859],[318,418,429,484,501,502,881],[71,318,418,429,484,501,502],[318,418,429,484,501,502,853,854,859,879,881,884,887,892,893,906,907],[261,429,484,501,502,840],[429,484,501,502,866,869,908],[429,484,501,502,893,905],[66,429,484,501,502,841,861,862,865,868,900,905,909,912,916,917,918,920,922,928,930],[318,418,429,484,501,502,847,855,858,859],[318,429,484,501,502,851],[295,318,343,418,429,484,501,502,850,851,852,853,858,859,861,931],[429,484,501,502,853,854,857,859,895,904],[318,418,429,484,501,502,846,858,859],[429,484,501,502,894],[418,429,484,501,502,854,859],[418,429,484,501,502,847,854,858,899],[318,343,418,429,484,501,502,846,858],[418,429,484,501,502,852,853,857,897,901,902,903],[418,429,484,501,502,847,854,855,856,858,859],[318,343,429,484,501,502,854,857,859],[261,429,484,501,502,858],[270,303,309,429,484,501,502],[429,484,501,502,843,844,845,854,858,859,898],[429,484,501,502,850,899,910,911],[343,418,429,484,501,502,859],[343,418,429,484,501,502],[429,484,501,502,842,843,844,845,848,850],[429,484,501,502,847],[429,484,501,502,849,850],[418,429,484,501,502,842,843,844,845,848,849],[429,484,501,502,885,886],[318,429,484,501,502,854,859,861,873],[429,484,501,502,896],[301,429,484,501,502],[282,318,429,484,501,502,913,914],[429,484,501,502,915],[318,429,484,501,502,861],[318,429,484,501,502,854,861],[295,318,418,429,484,501,502,847,854,855,856,858,859],[294,318,418,429,484,501,502,841,854,861,899,917],[295,296,418,429,484,501,502,840,919],[429,484,501,502,889,890,891],[418,429,484,501,502,888],[429,484,501,502,921],[418,429,484,501,502,514],[429,484,501,502,924,926,927],[429,484,501,502,923],[429,484,501,502,925],[418,429,484,501,502,853,858,924],[429,484,501,502,871],[318,343,418,429,484,501,502,854,858,859,861,896,897,899,900],[429,484,501,502,929],[429,484,501,502,826,828,829,830,831],[429,484,501,502,827],[418,429,484,501,502,826],[418,429,484,501,502,827],[429,484,501,502,826,828],[429,484,501,502,832],[318,418,429,484,498,499,500,501,502,801,821,840,970,973,976],[429,484,501,502,977],[429,484,501,502,979,980,981],[418,429,484,501,502,821],[429,484,501,502,976,978,982],[429,484,496,501,502,821],[429,484,501,502,971,972],[429,484,501,502,974,975],[418,429,484,501,502,801,821,970,973,974],[429,484,501,502,821],[429,484,501,502,535],[429,484,501,502,993,996],[429,484,501,502,993,994,995],[429,484,501,502,996],[429,484,489,501,502,535,825],[429,481,482,484,501,502],[429,483,484,501,502],[484,501,502],[429,484,489,501,502,520],[429,484,485,490,495,501,502,504,517,528],[429,484,485,486,495,501,502,504],[429,484,487,501,502,529],[429,484,488,489,496,501,502,506],[429,484,489,501,502,517,525],[429,484,490,492,495,501,502,504],[429,483,484,491,501,502],[429,484,492,493,501,502],[429,484,494,495,501,502],[429,483,484,495,501,502],[429,484,495,496,497,501,502,517,528],[429,484,495,496,497,501,502,512,517,520],[429,476,484,492,495,498,501,502,504,517,528],[429,484,495,496,498,499,501,502,504,517,525,528],[429,484,498,500,501,502,517,525,528],[427,428,429,430,431,432,433,434,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534],[429,484,495,501,502],[429,484,501,502,503,528],[429,484,492,495,501,502,504,517],[429,484,501,502,506],[429,484,501,502,507],[429,483,484,501,502,508],[429,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534],[429,484,501,502,510],[429,484,501,502,511],[429,484,495,501,502,512,513],[429,484,501,502,512,514,529,531],[429,484,496,501,502],[429,484,495,501,502,517,518,520],[429,484,501,502,519,520],[429,484,501,502,517,518],[429,484,501,502,520],[429,484,501,502,521],[429,481,484,501,502,517,522,528],[429,484,495,501,502,523,524],[429,484,501,502,523,524],[429,484,489,501,502,504,517,525],[429,484,501,502,526],[429,484,501,502,504,527],[429,484,498,501,502,511,528],[429,484,489,501,502,529],[429,484,501,502,517,530],[429,484,501,502,503,531],[429,484,501,502,532],[429,484,489,501,502],[429,476,484,501,502],[429,484,501,502,533],[429,476,484,495,497,501,502,508,517,520,528,530,531,533],[429,484,501,502,517,534],[429,484,501,502,606,607,608,609,610,611,612,613,614],[429,484,501,502,745,746,750,777,778,780,781,782,784,785],[429,484,501,502,743,744],[429,484,501,502,743],[429,484,501,502,745,785],[429,484,501,502,745,746,782,783,785],[429,484,501,502,785],[429,484,501,502,742,785,786],[429,484,501,502,745,746,784,785],[429,484,501,502,745,746,748,749,784,785],[429,484,501,502,745,746,747,784,785],[429,484,501,502,745,746,750,777,778,779,780,781,784,785],[429,484,501,502,745,750,779,780,781,782,784,785,794],[429,484,501,502,742,745,746,750,782,784],[429,484,501,502,750,785],[429,484,501,502,752,753,754,755,756,757,758,759,760,761,785],[429,484,501,502,775,785],[429,484,501,502,751,762,770,771,772,773,774,776],[429,484,501,502,775,785,787],[429,484,501,502,785,787],[429,484,501,502,785,788,789,790,791,792,793],[429,484,501,502,750,785,787],[429,484,501,502,755,785],[429,484,501,502,763,764,765,766,767,768,769,785],[429,484,501,502,834],[429,484,501,502,562],[429,484,501,502,564,565,566,567,568,569,570],[429,484,501,502,553],[429,484,501,502,554,562,563,571],[429,484,501,502,555],[429,484,501,502,549],[429,484,501,502,546,547,548,549,550,551,552,555,556,557,558,559,560,561],[429,484,501,502,554,556],[429,484,501,502,557,562],[429,484,501,502,578],[429,484,501,502,577,578,583],[429,484,501,502,579,580,581,582,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702],[429,484,501,502,578,615],[429,484,501,502,578,655],[429,484,501,502,577],[429,484,501,502,573,574,575,576,577,578,583,703,704,705,706,710],[429,484,501,502,583],[429,484,501,502,575,708,709],[429,484,501,502,577,707],[429,484,501,502,578,583],[429,484,501,502,573,574],[429,484,501,502,786],[429,484,498,499,500,501,502,504,796,797,799,800,801,802,803,804,805,810,811,812,813,814,815,816,817,818,819,820],[429,484,501,502,802,803,804,815,817],[429,484,501,502,802,815],[429,484,501,502,797],[429,484,501,502,517,797,802,803,804,805,810,811,812,813,815,817],[429,484,498,501,502,504,797,800,801,802,803,804,805,810,812,814,815,817,818,821],[429,484,501,502,797,802,803,804,805,809,813,815,817],[429,484,501,502,802,804,810,813],[429,484,501,502,802,810,811,813,821],[429,484,501,502,802,803,804,810,813,815,817],[429,484,501,502,796,802,803,804,810,813,815,816],[429,484,501,502,797,800,802,803,804,805,810,813,814,816,817],[429,484,501,502,796,799,821],[429,484,498,499,500,501,502,802],[429,484,501,502,802,803,815],[429,484,498,499,500,501,502],[429,484,498,499,501,502],[429,484,501,502,654],[429,484,498,501,502,517],[429,484,498,501,502,535],[429,484,495,501,502,533,806,807,808],[73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,89,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,129,130,131,132,133,134,135,136,137,138,139,140,142,143,144,145,146,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,192,193,194,196,205,207,208,209,210,211,212,214,215,217,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,429,484,501,502],[118,429,484,501,502],[74,77,429,484,501,502],[76,429,484,501,502],[76,77,429,484,501,502],[73,74,75,77,429,484,501,502],[74,76,77,234,429,484,501,502],[77,429,484,501,502],[73,76,118,429,484,501,502],[76,77,234,429,484,501,502],[76,242,429,484,501,502],[74,76,77,429,484,501,502],[86,429,484,501,502],[109,429,484,501,502],[130,429,484,501,502],[76,77,118,429,484,501,502],[77,125,429,484,501,502],[76,77,118,136,429,484,501,502],[76,77,136,429,484,501,502],[77,177,429,484,501,502],[77,118,429,484,501,502],[73,77,195,429,484,501,502],[73,77,196,429,484,501,502],[218,429,484,501,502],[202,204,429,484,501,502],[213,429,484,501,502],[202,429,484,501,502],[73,77,195,202,203,429,484,501,502],[195,196,204,429,484,501,502],[216,429,484,501,502],[73,77,202,203,204,429,484,501,502],[75,76,77,429,484,501,502],[73,77,429,484,501,502],[74,76,196,197,198,199,429,484,501,502],[118,196,197,198,199,429,484,501,502],[196,198,429,484,501,502],[76,197,198,200,201,205,429,484,501,502],[73,76,429,484,501,502],[77,220,429,484,501,502],[78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,126,127,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,429,484,501,502],[206,429,484,501,502],[429,484,495,501,502,535],[429,484,495,501,502,533],[429,441,444,447,448,484,501,502,528],[429,444,484,501,502,517,528],[429,444,448,484,501,502,528],[429,484,501,502,517],[429,438,484,501,502],[429,442,484,501,502],[429,440,441,444,484,501,502,528],[429,484,501,502,504,525],[429,438,484,501,502,535],[429,440,444,484,501,502,504,528],[429,435,436,437,439,443,484,495,501,502,517,528],[429,444,453,461,484,501,502],[429,436,442,484,501,502],[429,444,470,471,484,501,502],[429,436,439,444,484,501,502,520,528,535],[429,444,484,501,502],[429,440,444,484,501,502,528],[429,435,484,501,502],[429,438,439,440,442,443,444,445,446,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,471,472,473,474,475,484,501,502],[429,444,463,466,484,492,501,502],[429,444,453,454,455,484,501,502],[429,442,444,454,456,484,501,502],[429,443,484,501,502],[429,436,438,444,484,501,502],[429,444,448,454,456,484,501,502],[429,448,484,501,502],[429,442,444,447,484,501,502,528],[429,436,440,444,453,484,501,502],[429,444,463,484,501,502],[429,456,484,501,502],[429,438,444,470,484,501,502,520,533,535],[418,429,484,501,502,823,966],[418,429,484,501,502,938,966,967],[418,429,484,501,502,720,721,741],[418,429,484,501,502,545,712,716,719,937,938,941,945,950,955,960,965,968],[418,429,484,501,502,836],[418,429,484,501,502,717,824,836,933,934],[418,429,484,501,502,545,833,836,931,932,935,936,938],[418,429,484,489,501,502,715,721,722,741,833,835],[429,484,501,502,711],[418,429,484,501,502,823,824,947,948],[418,429,484,501,502,716,938,947,949],[418,429,484,501,502,715,738,741,946],[418,429,484,501,502,822],[418,429,484,501,502,717,741,822,833,931],[418,429,484,501,502,545,712],[429,484,501,502,572,711],[418,429,484,501,502,715],[418,429,484,496,501,502,507,545,713,714],[429,484,501,502,713],[418,429,484,501,502,823,824,951,953],[418,429,484,501,502,716,938,953,954],[418,429,484,501,502,715,721,738,741,952],[418,429,484,501,502,724,725,740,823,824,939],[418,429,484,501,502,716,739,740,938,940],[429,484,501,502,725],[429,484,501,502,725,735],[429,484,501,502,725,728,729,730,731,732,733,734],[429,484,501,502,721,725,726],[418,429,484,501,502,715,721,723,724,726,727,734,736,739,741],[418,429,484,501,502,715,738,741],[418,429,484,501,502,717],[418,429,484,501,502,718],[66,418,429,484,501,502,545,931,969,983,984],[429,484,501,502,711,721],[418,429,484,501,502,823,824,942,943],[418,429,484,501,502,716,938,943,944],[418,429,484,501,502,715,721,737,741],[418,429,484,501,502,715,721,741,988],[418,429,484,501,502,716,938,989],[429,484,501,502,721],[429,484,501,502,720,721],[429,484,501,502,721,737],[418,429,484,501,502,823,824,956,958],[418,429,484,501,502,716,938,958,959],[418,429,484,501,502,715,721,738,741,957],[418,429,484,501,502,741,823,824,836,837,838],[418,429,484,501,502,716,741,839,937,941],[418,429,484,501,502,715,721,722,740],[418,429,484,501,502,823,824,961,962,963],[418,429,484,501,502,716,938,963,964],[418,429,484,501,502,715,721,737,738,741]],"fileInfos":[{"version":"69684132aeb9b5642cbcd9e22dff7818ff0ee1aa831728af0ecf97d3364d5546","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"092c2bfe125ce69dbb1223c85d68d4d2397d7d8411867b5cc03cec902c233763","affectsGlobalScope":true,"impliedFormat":1},{"version":"07f073f19d67f74d732b1adea08e1dc66b1b58d77cb5b43931dee3d798a2fd53","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"4245fee526a7d1754529d19227ecbf3be066ff79ebb6a380d78e41648f2f224d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"8d6d51a5118d000ed3bfe6e1dd1335bebfff3fef23cd2af2f84a24d30f90cc90","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d8dedbec739bc79642c1e96e9bfc0b83b25b104a0486aebf016fc7b85b39f48","impliedFormat":1},{"version":"e89535c3ec439608bcd0f68af555d0e5ddf121c54abe69343549718bd7506b9c","impliedFormat":1},{"version":"622a984b60c294ffb2f9152cf1d4d12e91d2b733d820eec949cf54d63a3c1025","impliedFormat":1},{"version":"81aae92abdeaccd9c1723cef39232c90c1aed9d9cf199e6e2a523b7d8e058a11","impliedFormat":1},{"version":"a63a6c6806a1e519688ef7bd8ca57be912fc0764485119dbd923021eb4e79665","impliedFormat":1},{"version":"75b57b109d774acca1e151df21cf5cb54c7a1df33a273f0457b9aee4ebd36fb9","impliedFormat":1},{"version":"073ca26c96184db9941b5ec0ddea6981c9b816156d9095747809e524fdd90e35","impliedFormat":1},{"version":"e41d17a2ec23306d953cda34e573ed62954ca6ea9b8c8b74e013d07a6886ce47","impliedFormat":1},{"version":"241bd4add06f06f0699dcd58f3b334718d85e3045d9e9d4fa556f11f4d1569c1","impliedFormat":1},{"version":"2ae3787e1498b20aad1b9c2ee9ea517ec30e89b70d242d8e3e52d1e091039695","impliedFormat":1},{"version":"c7c72c4cffb1bc83617eefed71ed68cc89df73cab9e19507ccdecb3e72b4967e","affectsGlobalScope":true,"impliedFormat":1},{"version":"b8bff8a60af0173430b18d9c3e5c443eaa3c515617210c0c7b3d2e1743c19ecb","impliedFormat":1},{"version":"38b38db08e7121828294dec10957a7a9ff263e33e2a904b346516d4a4acca482","impliedFormat":1},{"version":"a76ebdf2579e68e4cfe618269c47e5a12a4e045c2805ed7f7ab37af8daa6b091","impliedFormat":1},{"version":"8a2aaea564939c22be05d665cc955996721bad6d43148f8fa21ae8f64afecd37","impliedFormat":1},{"version":"e59d36b7b6e8ba2dd36d032a5f5c279d2460968c8b4e691ca384f118fb09b52a","impliedFormat":1},{"version":"e96885c0684c9042ec72a9a43ef977f6b4b4a2728f4b9e737edcbaa0c74e5bf6","impliedFormat":1},{"version":"95950a187596e206d32d5d9c7b932901088c65ed8f9040e614aa8e321e0225ef","impliedFormat":1},{"version":"89e061244da3fc21b7330f4bd32f47c1813dd4d7f1dc3d0883d88943f035b993","impliedFormat":1},{"version":"e46558c2e04d06207b080138678020448e7fc201f3d69c2601b0d1456105f29a","impliedFormat":1},{"version":"71549375db52b1163411dba383b5f4618bdf35dc57fa327a1c7d135cf9bf67d1","impliedFormat":1},{"version":"7e6b2d61d6215a4e82ea75bc31a80ebb8ad0c2b37a60c10c70dd671e8d9d6d5d","impliedFormat":1},{"version":"78bea05df2896083cca28ed75784dde46d4b194984e8fc559123b56873580a23","impliedFormat":1},{"version":"5dd04ced37b7ea09f29d277db11f160df7fd73ba8b9dba86cb25552e0653a637","impliedFormat":1},{"version":"f74b81712e06605677ae1f061600201c425430151f95b5ef4d04387ad7617e6a","impliedFormat":1},{"version":"9a72847fcf4ac937e352d40810f7b7aec7422d9178451148296cf1aa19467620","impliedFormat":1},{"version":"3ae18f60e0b96fa1e025059b7d25b3247ba4dcb5f4372f6d6e67ce2adac74eac","impliedFormat":1},{"version":"2b9260f44a2e071450ae82c110f5dc8f330c9e5c3e85567ed97248330f2bf639","impliedFormat":1},{"version":"4f196e13684186bda6f5115fc4677a87cf84a0c9c4fc17b8f51e0984f3697b6d","impliedFormat":1},{"version":"61419f2c5822b28c1ea483258437c1faab87d00c6f84481aa22afb3380d8e9a4","impliedFormat":1},{"version":"64479aee03812264e421c0bf5104a953ca7b02740ba80090aead1330d0effe91","impliedFormat":1},{"version":"0521108c9f8ddb17654a0a54dae6ba9667c99eddccfd6af5748113e022d1c37a","impliedFormat":1},{"version":"c5570e504be103e255d80c60b56c367bf45d502ca52ee35c55dec882f6563b5c","impliedFormat":1},{"version":"ee764e6e9a7f2b987cc1a2c0a9afd7a8f4d5ebc4fdb66ad557a7f14a8c2bd320","impliedFormat":1},{"version":"0520b5093712c10c6ef23b5fea2f833bf5481771977112500045e5ea7e8e2b69","impliedFormat":1},{"version":"5c3cf26654cf762ac4d7fd7b83f09acfe08eef88d2d6983b9a5a423cb4004ca3","impliedFormat":1},{"version":"e60fa19cf7911c1623b891155d7eb6b7e844e9afdf5738e3b46f3b687730a2bd","impliedFormat":1},{"version":"b1fd72ff2bb0ba91bb588f3e5329f8fc884eb859794f1c4657a2bfa122ae54d0","impliedFormat":1},{"version":"6cf42a4f3cfec648545925d43afaa8bb364ac10a839ffed88249da109361b275","impliedFormat":1},{"version":"d7058e75920120b142a9d57be25562a3cd9a936269fd52908505f530105f2ec4","impliedFormat":1},{"version":"6df52b70d7f7702202f672541a5f4a424d478ee5be51a9d37b8ccbe1dbf3c0f2","impliedFormat":1},{"version":"0ca7f997e9a4d8985e842b7c882e521b6f63233c4086e9fe79dd7a9dc4742b5e","impliedFormat":1},{"version":"91046b5c6b55d3b194c81fd4df52f687736fad3095e9d103ead92bb64dc160ee","impliedFormat":1},{"version":"db5704fdad56c74dfc5941283c1182ed471bd17598209d3ac4a49faa72e43cfc","impliedFormat":1},{"version":"758e8e89559b02b81bc0f8fd395b17ad5aff75490c862cbe369bb1a3d1577c40","impliedFormat":1},{"version":"2ee64342c077b1868f1834c063f575063051edd6e2964257d34aad032d6b657c","impliedFormat":1},{"version":"6f6b4b3d670b6a5f0e24ea001c1b3d36453c539195e875687950a178f1730fa7","impliedFormat":1},{"version":"a472a1d3f25ce13a1d44911cd3983956ac040ce2018e155435ea34afb25f864c","impliedFormat":1},{"version":"b48b83a86dd9cfe36f8776b3ff52fcd45b0e043c0538dc4a4b149ba45fe367b9","impliedFormat":1},{"version":"792de5c062444bd2ee0413fb766e57e03cce7cdaebbfc52fc0c7c8e95069c96b","impliedFormat":1},{"version":"a79e3e81094c7a04a885bad9b049c519aace53300fb8a0fe4f26727cb5a746ce","impliedFormat":1},{"version":"93181bac0d90db185bb730c95214f6118ae997fe836a98a49664147fbcaf1988","impliedFormat":1},{"version":"8a4e89564d8ea66ad87ee3762e07540f9f0656a62043c910d819b4746fc429c5","impliedFormat":1},{"version":"b9011d99942889a0f95e120d06b698c628b0b6fdc3e6b7ecb459b97ed7d5bcc6","impliedFormat":1},{"version":"4d639cbbcc2f8f9ce6d55d5d503830d6c2556251df332dc5255d75af53c8a0e7","impliedFormat":1},{"version":"cdb48277f600ab5f429ecf1c5ea046683bc6b9f73f3deab9a100adac4b34969c","impliedFormat":1},{"version":"75be84956a29040a1afbe864c0a7a369dfdb739380072484eff153905ef867ee","impliedFormat":1},{"version":"b06b4adc2ae03331a92abd1b19af8eb91ec2bf8541747ee355887a167d53145e","impliedFormat":1},{"version":"c54166a85bd60f86d1ebb90ce0117c0ecb850b8a33b366691629fdf26f1bbbd8","impliedFormat":1},{"version":"0d417c15c5c635384d5f1819cc253a540fe786cc3fda32f6a2ae266671506a21","impliedFormat":1},{"version":"80f23f1d60fbed356f726b3b26f9d348dddbb34027926d10d59fad961e70a730","impliedFormat":1},{"version":"cb59317243a11379a101eb2f27b9df1022674c3df1df0727360a0a3f963f523b","impliedFormat":1},{"version":"cc20bb2227dd5de0aab0c8d697d1572f8000550e62c7bf5c92f212f657dd88c5","impliedFormat":1},{"version":"06b8a7d46195b6b3980e523ef59746702fd210b71681a83a5cf73799623621f9","impliedFormat":1},{"version":"860e4405959f646c101b8005a191298b2381af8f33716dc5f42097e4620608f8","impliedFormat":1},{"version":"f7e32adf714b8f25d3c1783473abec3f2e82d5724538d8dcf6f51baaaff1ca7a","impliedFormat":1},{"version":"d0da80c845999a16c24d0783033fb5366ada98df17867c98ad433ede05cd87fd","impliedFormat":1},{"version":"bfbf80f9cd4558af2d7b2006065340aaaced15947d590045253ded50aabb9bc5","impliedFormat":1},{"version":"fd9a991b51870325e46ebb0e6e18722d313f60cd8e596e645ec5ac15b96dbf4e","impliedFormat":1},{"version":"c3bd2b94e4298f81743d92945b80e9b56c1cdfb2bef43c149b7106a2491b1fc9","impliedFormat":1},{"version":"a246cce57f558f9ebaffd55c1e5673da44ea603b4da3b2b47eb88915d30a9181","impliedFormat":1},{"version":"d993eacc103c5a065227153c9aae8acea3a4322fe1a169ee7c70b77015bf0bb2","impliedFormat":1},{"version":"fc2b03d0c042aa1627406e753a26a1eaad01b3c496510a78016822ef8d456bb6","impliedFormat":1},{"version":"063c7ebbe756f0155a8b453f410ca6b76ffa1bbc1048735bcaf9c7c81a1ce35f","impliedFormat":1},{"version":"314e402cd481370d08f63051ae8b8c8e6370db5ee3b8820eeeaaf8d722a6dac6","impliedFormat":1},{"version":"9669075ac38ce36b638b290ba468233980d9f38bdc62f0519213b2fd3e2552ec","impliedFormat":1},{"version":"4d123de012c24e2f373925100be73d50517ac490f9ed3578ac82d0168bfbd303","impliedFormat":1},{"version":"656c9af789629aa36b39092bee3757034009620439d9a39912f587538033ce28","impliedFormat":1},{"version":"3ac3f4bdb8c0905d4c3035d6f7fb20118c21e8a17bee46d3735195b0c2a9f39f","impliedFormat":1},{"version":"1f453e6798ed29c86f703e9b41662640d4f2e61337007f27ac1c616f20093f69","impliedFormat":1},{"version":"af43b7871ff21c62bf1a54ec5c488e31a8d3408d5b51ff2e9f8581b6c55f2fc7","impliedFormat":1},{"version":"70550511d25cbb0b6a64dcac7fffc3c1397fd4cbeb6b23ccc7f9b794ab8a6954","impliedFormat":1},{"version":"af0fbf08386603a62f2a78c42d998c90353b1f1d22e05a384545f7accf881e0a","impliedFormat":1},{"version":"cefc20054d20b85b534206dbcedd509bb74f87f3d8bc45c58c7be3a76caa45e1","impliedFormat":1},{"version":"ad6eee4877d0f7e5244d34bc5026fd6e9cf8e66c5c79416b73f9f6ebf132f924","impliedFormat":1},{"version":"4888fd2bcfee9a0ce89d0df860d233e0cee8ee9c479b6bd5a5d5f9aae98342fe","impliedFormat":1},{"version":"f4749c102ced952aa6f40f0b579865429c4869f6d83df91000e98005476bee87","impliedFormat":1},{"version":"56654d2c5923598384e71cb808fac2818ca3f07dd23bb018988a39d5e64f268b","impliedFormat":1},{"version":"8b6719d3b9e65863da5390cb26994602c10a315aa16e7d70778a63fee6c4c079","impliedFormat":1},{"version":"05f56cd4b929977d18df8f3d08a4c929a2592ef5af083e79974b20a063f30940","impliedFormat":1},{"version":"547d3c406a21b30e2b78629ecc0b2ddaf652d9e0bdb2d59ceebce5612906df33","impliedFormat":1},{"version":"b3a4f9385279443c3a5568ec914a9492b59a723386161fd5ef0619d9f8982f97","impliedFormat":1},{"version":"3fe66aba4fbe0c3ba196a4f9ed2a776fe99dc4d1567a558fb11693e9fcc4e6ed","impliedFormat":1},{"version":"140eef237c7db06fc5adcb5df434ee21e81ee3a6fd57e1a75b8b3750aa2df2d8","impliedFormat":1},{"version":"0944ec553e4744efae790c68807a461720cff9f3977d4911ac0d918a17c9dd99","impliedFormat":1},{"version":"cb46b38d5e791acaa243bf342b8b5f8491639847463ac965b93896d4fb0af0d9","impliedFormat":1},{"version":"7c7d9e116fe51100ff766703e6b5e4424f51ad8977fe474ddd8d0959aa6de257","impliedFormat":1},{"version":"af70a2567e586be0083df3938b6a6792e6821363d8ef559ad8d721a33a5bcdaf","impliedFormat":1},{"version":"006cff3a8bcb92d77953f49a94cd7d5272fef4ab488b9052ef82b6a1260d870b","impliedFormat":1},{"version":"7d44bfdc8ee5e9af70738ff652c622ae3ad81815e63ab49bdc593d34cb3a68e5","impliedFormat":1},{"version":"339814517abd4dbc7b5f013dfd3b5e37ef0ea914a8bbe65413ecffd668792bc6","impliedFormat":1},{"version":"34d5bc0a6958967ec237c99f980155b5145b76e6eb927c9ffc57d8680326b5d8","impliedFormat":1},{"version":"9eae79b70c9d8288032cbe1b21d0941f6bd4f315e14786b2c1d10bccc634e897","impliedFormat":1},{"version":"18ce015ed308ea469b13b17f99ce53bbb97975855b2a09b86c052eefa4aa013a","impliedFormat":1},{"version":"5a931bc4106194e474be141e0bc1046629510dc95b9a0e4b02a3783847222965","impliedFormat":1},{"version":"5e5f371bf23d5ced2212a5ff56675aefbd0c9b3f4d4fdda1b6123ac6e28f058c","impliedFormat":1},{"version":"907c17ad5a05eecb29b42b36cc8fec6437be27cc4986bb3a218e4f74f606911c","impliedFormat":1},{"version":"ce60a562cd2a92f37a88f2ddd99a3abfbc5848d7baf38c48fb8d3243701fcb75","impliedFormat":1},{"version":"a726ad2d0a98bfffbe8bc1cd2d90b6d831638c0adc750ce73103a471eb9a891c","impliedFormat":1},{"version":"f44c0c8ce58d3dacac016607a1a90e5342d830ea84c48d2e571408087ae55894","impliedFormat":1},{"version":"75a315a098e630e734d9bc932d9841b64b30f7a349a20cf4717bf93044eff113","impliedFormat":1},{"version":"9131d95e32b3d4611d4046a613e022637348f6cebfe68230d4e81b691e4761a1","impliedFormat":1},{"version":"b03aa292cfdcd4edc3af00a7dbd71136dd067ec70a7536b655b82f4dd444e857","impliedFormat":1},{"version":"b6e2b0448ced813b8c207810d96551a26e7d7bb73255eea4b9701698f78846d6","impliedFormat":1},{"version":"8ae10cd85c1bd94d2f2d17c4cbd25c068a4b2471c70c2d96434239f97040747a","impliedFormat":1},{"version":"9ed5b799c50467b0c9f81ddf544b6bcda3e34d92076d6cab183c84511e45c39f","impliedFormat":1},{"version":"b4fa87cc1833839e51c49f20de71230e259c15b2c9c3e89e4814acc1d1ef10de","impliedFormat":1},{"version":"e90ac9e4ac0326faa1bc39f37af38ace0f9d4a655cd6d147713c653139cf4928","impliedFormat":1},{"version":"ea27110249d12e072956473a86fd1965df8e1be985f3b686b4e277afefdde584","impliedFormat":1},{"version":"8776a368617ce51129b74db7d55c3373dadcce5d0701e61d106e99998922a239","impliedFormat":1},{"version":"5666075052877fe2fdddd5b16de03168076cf0f03fbca5c1d4a3b8f43cba570c","impliedFormat":1},{"version":"9108ab5af05418f599ab48186193b1b07034c79a4a212a7f73535903ba4ca249","impliedFormat":1},{"version":"bb4e2cdcadf9c9e6ee2820af23cee6582d47c9c9c13b0dca1baaffe01fbbcb5f","impliedFormat":1},{"version":"6e30d0b5a1441d831d19fe02300ab3d83726abd5141cbcc0e2993fa0efd33db4","impliedFormat":1},{"version":"423f28126b2fc8d8d6fa558035309000a1297ed24473c595b7dec52e5c7ebae5","impliedFormat":1},{"version":"fb30734f82083d4790775dae393cd004924ebcbfde49849d9430bf0f0229dd16","impliedFormat":1},{"version":"2c92b04a7a4a1cd9501e1be338bf435738964130fb2ad5bd6c339ee41224ac4c","impliedFormat":1},{"version":"c5c5f0157b41833180419dacfbd2bcce78fb1a51c136bd4bcba5249864d8b9b5","impliedFormat":1},{"version":"02ae43d5bae42efcd5a00d3923e764895ce056bca005a9f4e623aa6b4797c8af","impliedFormat":1},{"version":"db6e01f17012a9d7b610ae764f94a1af850f5d98c9c826ad61747dca0fb800bd","impliedFormat":1},{"version":"8a44b424edee7bb17dc35a558cc15f92555f14a0441205613e0e50452ab3a602","impliedFormat":1},{"version":"24a00d0f98b799e6f628373249ece352b328089c3383b5606214357e9107e7d5","impliedFormat":1},{"version":"33637e3bc64edd2075d4071c55d60b32bdb0d243652977c66c964021b6fc8066","impliedFormat":1},{"version":"0f0ad9f14dedfdca37260931fac1edf0f6b951c629e84027255512f06a6ebc4c","impliedFormat":1},{"version":"16ad86c48bf950f5a480dc812b64225ca4a071827d3d18ffc5ec1ae176399e36","impliedFormat":1},{"version":"8cbf55a11ff59fd2b8e39a4aa08e25c5ddce46e3af0ed71fb51610607a13c505","impliedFormat":1},{"version":"d5bc4544938741f5daf8f3a339bfbf0d880da9e89e79f44a6383aaf056fe0159","impliedFormat":1},{"version":"97f9169882d393e6f303f570168ca86b5fe9aab556e9a43672dae7e6bb8e6495","impliedFormat":1},{"version":"7c9adb3fcd7851497818120b7e151465406e711d6a596a71b807f3a17853cb58","impliedFormat":1},{"version":"6752d402f9282dd6f6317c8c048aaaac27295739a166eed27e00391b358fed9a","impliedFormat":1},{"version":"9fd7466b77020847dbc9d2165829796bf7ea00895b2520ff3752ffdcff53564b","impliedFormat":1},{"version":"fbfc12d54a4488c2eb166ed63bab0fb34413e97069af273210cf39da5280c8d6","impliedFormat":1},{"version":"85a84240002b7cf577cec637167f0383409d086e3c4443852ca248fc6e16711e","impliedFormat":1},{"version":"84794e3abd045880e0fadcf062b648faf982aa80cfc56d28d80120e298178626","impliedFormat":1},{"version":"053d8b827286a16a669a36ffc8ccc8acdf8cc154c096610aa12348b8c493c7b8","impliedFormat":1},{"version":"3cce4ce031710970fe12d4f7834375f5fd455aa129af4c11eb787935923ff551","impliedFormat":1},{"version":"8f62cbd3afbd6a07bb8c934294b6bfbe437021b89e53a4da7de2648ecfc7af25","impliedFormat":1},{"version":"62c3621d34fb2567c17a2c4b89914ebefbfbd1b1b875b070391a7d4f722e55dc","impliedFormat":1},{"version":"c05ac811542e0b59cb9c2e8f60e983461f0b0e39cea93e320fad447ff8e474f3","impliedFormat":1},{"version":"8e7a5b8f867b99cc8763c0b024068fb58e09f7da2c4810c12833e1ca6eb11c4f","impliedFormat":1},{"version":"132351cbd8437a463757d3510258d0fa98fd3ebef336f56d6f359cf3e177a3ce","impliedFormat":1},{"version":"df877050b04c29b9f8409aa10278d586825f511f0841d1ec41b6554f8362092b","impliedFormat":1},{"version":"33d1888c3c27d3180b7fd20bac84e97ecad94b49830d5dd306f9e770213027d1","impliedFormat":1},{"version":"ee942c58036a0de88505ffd7c129f86125b783888288c2389330168677d6347f","impliedFormat":1},{"version":"a3f317d500c30ea56d41501632cdcc376dae6d24770563a5e59c039e1c2a08ec","impliedFormat":1},{"version":"eb21ddc3a8136a12e69176531197def71dc28ffaf357b74d4bf83407bd845991","impliedFormat":1},{"version":"0c1651a159995dfa784c57b4ea9944f16bdf8d924ed2d8b3db5c25d25749a343","impliedFormat":1},{"version":"aaa13958e03409d72e179b5d7f6ec5c6cc666b7be14773ae7b6b5ee4921e52db","impliedFormat":1},{"version":"0a86e049843ad02977a94bb9cdfec287a6c5a0a4b6b5391a6648b1a122072c5a","impliedFormat":1},{"version":"40f06693e2e3e58526b713c937895c02e113552dc8ba81ecd49cdd9596567ddb","impliedFormat":1},{"version":"4ed5e1992aedb174fb8f5aa8796aa6d4dcb8bd819b4af1b162a222b680a37fa0","impliedFormat":1},{"version":"d7f4bd46a8b97232ea6f8c28012b8d2b995e55e729d11405f159d3e00c51420a","impliedFormat":1},{"version":"d604d413aff031f4bfbdae1560e54ebf503d374464d76d50a2c6ded4df525712","impliedFormat":1},{"version":"e4f4f9cf1e3ac9fd91ada072e4d428ecbf0aa6dc57138fb797b8a0ca3a1d521c","impliedFormat":1},{"version":"12bfd290936824373edda13f48a4094adee93239b9a73432db603127881a300d","impliedFormat":1},{"version":"340ceb3ea308f8e98264988a663640e567c553b8d6dc7d5e43a8f3b64f780374","impliedFormat":1},{"version":"c5a769564e530fba3ec696d0a5cff1709b9095a0bdf5b0826d940d2fc9786413","impliedFormat":1},{"version":"7124ef724c3fc833a17896f2d994c368230a8d4b235baed39aa8037db31de54f","impliedFormat":1},{"version":"5de1c0759a76e7710f76899dcae601386424eab11fb2efaf190f2b0f09c3d3d3","impliedFormat":1},{"version":"9c5ee8f7e581f045b6be979f062a61bf076d362bf89c7f966b993a23424e8b0d","impliedFormat":1},{"version":"1a11df987948a86aa1ec4867907c59bdf431f13ed2270444bf47f788a5c7f92d","impliedFormat":1},{"version":"8018dd2e95e7ce6e613ddd81672a54532614dc745520a2f9e3860ff7fb1be0ca","impliedFormat":1},{"version":"b756781cd40d465da57d1fc6a442c34ae61fe8c802d752aace24f6a43fedacee","impliedFormat":1},{"version":"0fe76167c87289ea094e01616dcbab795c11b56bad23e1ef8aba9aa37e93432a","impliedFormat":1},{"version":"3a45029dba46b1f091e8dc4d784e7be970e209cd7d4ff02bd15270a98a9ba24b","impliedFormat":1},{"version":"032c1581f921f8874cf42966f27fd04afcabbb7878fa708a8251cac5415a2a06","impliedFormat":1},{"version":"69c68ed9652842ce4b8e495d63d2cd425862104c9fb7661f72e7aa8a9ef836f8","impliedFormat":1},{"version":"0e704ee6e9fd8b6a5a7167886f4d8915f4bc22ed79f19cb7b32bd28458f50643","impliedFormat":1},{"version":"06f62a14599a68bcde148d1efd60c2e52e8fa540cc7dcfa4477af132bb3de271","impliedFormat":1},{"version":"904a96f84b1bcee9a7f0f258d17f8692e6652a0390566515fe6741a5c6db8c1c","impliedFormat":1},{"version":"11f19ce32d21222419cecab448fa335017ebebf4f9e5457c4fa9df42fa2dcca7","impliedFormat":1},{"version":"2e8ee2cbb5e9159764e2189cf5547aebd0e6b0d9a64d479397bb051cd1991744","impliedFormat":1},{"version":"1b0471d75f5adb7f545c1a97c02a0f825851b95fe6e069ac6ecaa461b8bb321d","impliedFormat":1},{"version":"1d157c31a02b1e5cca9bc495b3d8d39f4b42b409da79f863fb953fbe3c7d4884","impliedFormat":1},{"version":"07baaceaec03d88a4b78cb0651b25f1ae0322ac1aa0b555ae3749a79a41cba86","impliedFormat":1},{"version":"619a132f634b4ebe5b4b4179ea5870f62f2cb09916a25957bff17b408de8b56d","impliedFormat":1},{"version":"f60fa446a397eb1aead9c4e568faf2df8068b4d0306ebc075fb4be16ed26b741","impliedFormat":1},{"version":"f3cb784be4d9e91f966a0b5052a098d9b53b0af0d341f690585b0cc05c6ca412","impliedFormat":1},{"version":"350f63439f8fe2e06c97368ddc7fb6d6c676d54f59520966f7dbbe6a4586014e","impliedFormat":1},{"version":"eba613b9b357ac8c50a925fa31dc7e65ff3b95a07efbaa684b624f143d8d34ba","impliedFormat":1},{"version":"45b74185005ed45bec3f07cac6e4d68eaf02ead9ff5a66721679fb28020e5e7c","impliedFormat":1},{"version":"0f6199602df09bdb12b95b5434f5d7474b1490d2cd8cc036364ab3ba6fd24263","impliedFormat":1},{"version":"c8ca7fd9ec7a3ec82185bfc8213e4a7f63ae748fd6fced931741d23ef4ea3c0f","impliedFormat":1},{"version":"5c6a8a3c2a8d059f0592d4eab59b062210a1c871117968b10797dee36d991ef7","impliedFormat":1},{"version":"ad77fd25ece8e09247040826a777dc181f974d28257c9cd5acb4921b51967bd8","impliedFormat":1},{"version":"795a08ae4e193f345073b49f68826ab6a9b280400b440906e4ec5c237ae777e6","impliedFormat":1},{"version":"8153df63cf65122809db17128e5918f59d6bb43a371b5218f4430c4585f64085","impliedFormat":1},{"version":"a8150bc382dd12ce58e00764d2366e1d59a590288ee3123af8a4a2cb4ef7f9df","impliedFormat":1},{"version":"5adfaf2f9f33957264ad199a186456a4676b2724ed700fc313ff945d03372169","impliedFormat":1},{"version":"d5c41a741cd408c34cb91f84468f70e9bda3dfeabf33251a61039b3cdb8b22d8","impliedFormat":1},{"version":"a20c3e0fe86a1d8fc500a0e9afec9a872ad3ab5b746ceb3dd7118c6d2bff4328","impliedFormat":1},{"version":"cbaf4a4aa8a8c02aa681c5870d5c69127974de29b7e01df570edec391a417959","impliedFormat":1},{"version":"c7135e329a18b0e712378d5c7bc2faec6f5ab0e955ea0002250f9e232af8b3e4","impliedFormat":1},{"version":"340a45cd77b41d8a6deda248167fa23d3dc67ec798d411bd282f7b3d555b1695","impliedFormat":1},{"version":"fae330f86bc10db6841b310f32367aaa6f553036a3afc426e0389ddc5566cd74","impliedFormat":1},{"version":"2bee1efe53481e93bb8b31736caba17353e7bb6fc04520bd312f4e344afd92f9","impliedFormat":1},{"version":"357b67529139e293a0814cb5b980c3487717c6fbf7c30934d67bc42dad316871","impliedFormat":1},{"version":"99d99a765426accf8133737843fb024a154dc6545fc0ffbba968a7c0b848959d","impliedFormat":1},{"version":"c782c5fd5fa5491c827ecade05c3af3351201dd1c7e77e06711c8029b7a9ee4d","impliedFormat":1},{"version":"883d2104e448bb351c49dd9689a7e8117b480b614b2622732655cef03021bf6d","impliedFormat":1},{"version":"d9b00ee2eca9b149663fdba1c1956331841ae296ee03eaaff6c5becbc0ff1ea8","impliedFormat":1},{"version":"09a7e04beb0547c43270b327c067c85a4e2154372417390731dfe092c4350998","impliedFormat":1},{"version":"eee530aaa93e9ec362e3941ee8355e2d073c7b21d88c2af4713e3d701dab8fef","impliedFormat":1},{"version":"28d47319b97dbeee9130b78eae03b2061d46dedbf92b0d9de13ed7ab8399ccd0","impliedFormat":1},{"version":"6559a36671052ca93cab9a289279a6cef6f9d1a72c34c34546a8848274a9c66c","impliedFormat":1},{"version":"7a0e4cd92545ad03910fd019ae9838718643bd4dde39881c745f236914901dfa","impliedFormat":1},{"version":"c99ebd20316217e349004ee1a0bc74d32d041fb6864093f10f31984c737b8cad","impliedFormat":1},{"version":"6f622e7f054f5ab86258362ac0a64a2d6a27f1e88732d6f5f052f422e08a70e7","impliedFormat":1},{"version":"d62d2ef93ceeb41cf9dfab25989a1e5f9ca5160741aac7f1453c69a6c14c69be","impliedFormat":1},{"version":"1491e80d72873fc586605283f2d9056ee59b166333a769e64378240df130d1c9","impliedFormat":1},{"version":"c32c073d389cfaa3b3e562423e16c2e6d26b8edebbb7d73ccffff4aa66f2171d","impliedFormat":1},{"version":"eca72bf229eecadb63e758613c62fab13815879053539a22477d83a48a21cd73","impliedFormat":1},{"version":"633db46fd1765736409a4767bfc670861468dde60dbb9a501fba4c1b72f8644d","impliedFormat":1},{"version":"f379412f2c0dddd193ff66dcdd9d9cc169162e441d86804c98c84423f993aa8a","impliedFormat":1},{"version":"f2ee748883723aa9325e5d7f30fce424f6a786706e1b91a5a55237c78ee89c4a","impliedFormat":1},{"version":"eda4760e5d7b171132265e970b67c322bcfffacb84248f44def26ed160eb722e","impliedFormat":1},{"version":"142f5190d730259339be1433931c0eb31ae7c7806f4e325f8a470bd9221b6533","impliedFormat":1},{"version":"cbd19f594f0ee7beffeb37dc0367af3908815acf4ce46d86b0515478718cfed8","impliedFormat":1},{"version":"3cdb96f128133efd129c798ac11f959e59d278ae439f69983224774d79ed11db","impliedFormat":1},{"version":"8776e64e6165838ac152fa949456732755b0976d1867ae5534ce248f0ccd7f41","impliedFormat":1},{"version":"896bbc7402b3a403cda96813c8ea595470ff76d31f32869d053317c00ca2589a","impliedFormat":1},{"version":"5c4c5b49bbb01828402bb04af1d71673b18852c11b7e95bfd5cf4c3d80d352c8","impliedFormat":1},{"version":"7030df3d920343df00324df59dc93a959a33e0f4940af3fefef8c07b7ee329bf","impliedFormat":1},{"version":"a96bc00e0c356e29e620eaec24a56d6dd7f4e304feefcc99066a1141c6fe05a7","impliedFormat":1},{"version":"d12cc0e5b09943c4cd0848f787eb9d07bf78b60798e4588c50582db9d4decc70","impliedFormat":1},{"version":"7333ee6354964fd396297958e52e5bf62179aa2c88ca0a35c6d3a668293b7e0e","impliedFormat":1},{"version":"19c3760af3cbc9da99d5b7763b9e33aaf8d018bc2ed843287b7ff4343adf4634","impliedFormat":1},{"version":"9d1e38aeb76084848d2fcd39b458ec88246de028c0f3f448b304b15d764b23d2","impliedFormat":1},{"version":"d406da1eccf18cec56fd29730c24af69758fe3ff49c4f94335e797119cbc0554","impliedFormat":1},{"version":"4898c93890a136da9156c75acd1a80a941a961b3032a0cf14e1fa09a764448b7","impliedFormat":1},{"version":"f5d7a845e3e1c6c27351ea5f358073d0b0681537a2da6201fab254aa434121d3","impliedFormat":1},{"version":"3a47d4582ef0697cccf1f3d03b620002f03fb0ff098f630e284433c417d6c61b","impliedFormat":1},{"version":"d7c30f0abfe9e197e376b016086cf66b2ffb84015139963f37301ed0da9d3d0d","impliedFormat":1},{"version":"ff75bba0148f07775bcb54bf4823421ed4ebdb751b3bf79cc003bd22e49d7d73","impliedFormat":1},{"version":"d40d20ac633703a7333770bfd60360126fc3302d5392d237bbb76e8c529a4f95","impliedFormat":1},{"version":"35a9867207c488061fb4f6fe4715802fbc164b4400018d2fa0149ad02db9a61c","impliedFormat":1},{"version":"b5fd805b7c578ca6a42c42bbfa6fda95a85d9e332106d810bb18116dc13a45f8","impliedFormat":1},{"version":"3abd9ab4fb3a035c865e6a68cb9f4260515354d5ebebacd5c681aee52c046d1f","impliedFormat":1},{"version":"13e82862532619a727cff9a9ba78df7ca66e8a9b69e4cbd18e9809257b6bf7ba","impliedFormat":1},{"version":"601fe4e366b99181cd0244d96418cffeaaa987a7e310c6f0ed0f06ce63dfe3e9","impliedFormat":1},{"version":"c66a4f2b1362abc4aeee0870c697691618b423c8c6e75624a40ef14a06f787b7","impliedFormat":1},{"version":"8808b1c4f84f2e43da98757a959fe7282cb1795737e16534a97b7d4d33e84dfc","impliedFormat":1},{"version":"cd0565ace87a2d7802bf4c20ea23a997c54e598b9eb89f9c75e69478c1f7a0b4","impliedFormat":1},{"version":"738020d2c8fc9df92d5dee4b682d35a776eaedfe2166d12bc8f186e1ea57cc52","impliedFormat":1},{"version":"86dd7c5657a0b0bc6bee8002edcfd544458d3d3c60974555746eb9b2583dc35e","impliedFormat":1},{"version":"d97b96b6ecd4ee03f9f1170722c825ef778430a6a0d7aab03b8929012bf773cd","impliedFormat":1},{"version":"e84e9b89251a57da26a339e75f4014f52e8ef59b77c2ee1e0171cde18d17b3b8","impliedFormat":1},{"version":"272dbfe04cfa965d6fff63fdaba415c1b5a515b1881ae265148f8a84ddeb318f","impliedFormat":1},{"version":"2035fb009b5fafa9a4f4e3b3fdb06d9225b89f2cbbf17a5b62413bf72cea721a","impliedFormat":1},{"version":"eefafec7c059f07b885b79b327d381c9a560e82b439793de597441a4e68d774a","impliedFormat":1},{"version":"72636f59b635c378dc9ea5246b9b3517b1214e340e468e54cb80126353053b2e","impliedFormat":1},{"version":"ebb79f267a3bf2de5f8edc1995c5d31777b539935fab8b7d863e8efb06c8e9ea","impliedFormat":1},{"version":"ada033e6a4c7f4e147e6d76bb881069dc66750619f8cc2472d65beeec1100145","impliedFormat":1},{"version":"0c04cc14a807a5dc0e3752d18a3b2655a135fefbf76ddcdabd0c5df037530d41","impliedFormat":1},{"version":"605d29d619180fbec287d1701e8b1f51f2d16747ec308d20aba3e9a0dac43a0f","impliedFormat":1},{"version":"67c19848b442d77c767414084fc571ce118b08301c4ddff904889d318f3a3363","impliedFormat":1},{"version":"c704ff0e0cb86d1b791767a88af21dadfee259180720a14c12baee668d0eb8fb","impliedFormat":1},{"version":"195c50e15d5b3ea034e01fbdca6f8ad4b35ad47463805bb0360bdffd6fce3009","impliedFormat":1},{"version":"da665f00b6877ae4adb39cd548257f487a76e3d99e006a702a4f38b4b39431cb","impliedFormat":1},{"version":"083aebdd7c96aee90b71ec970f81c48984d9c8ab863e7d30084f048ddcc9d6af","impliedFormat":1},{"version":"9324b243d75bd794f7eaf0704161628dac1f536375b82ac9bebd6e5283a985ba","impliedFormat":1},{"version":"d7a4309673b06223537bc9544b1a5fe9425628e1c8ab5605f3c5ebc27ecb8074","impliedFormat":1},{"version":"0be3da88f06100e2291681bbda2592816dd804004f0972296b20725138ebcddf","impliedFormat":1},{"version":"3eadfd083d40777b403f4f4eecfa40f93876f2a01779157cc114b2565a7afb51","impliedFormat":1},{"version":"cb6789ce3eba018d5a7996ccbf50e27541d850e9b4ee97fdcb3cbd8c5093691f","impliedFormat":1},{"version":"a3684ea9719122f9477902acd08cd363a6f3cff6d493df89d4dc12fa58204e27","impliedFormat":1},{"version":"ff3c48a17bf10dfbb62448152042e4a48a56c9972059997ab9e7ed03b191809b","impliedFormat":1},{"version":"bc3561e460de5a2c19123f618fc1d5a96a484d168884d00666997d847f502bf9","impliedFormat":1},{"version":"c0c46113b4cd5ec9e7cf56e6dbfb3930ef6cbba914c0883eeced396988ae8320","impliedFormat":1},{"version":"118ea3f4e7b9c12e92551be0766706f57a411b4f18a1b4762cfde3cd6d4f0a96","impliedFormat":1},{"version":"01acd7f315e2493395292d9a02841f3b0300e77ccf42f84f4f11460e7623107d","impliedFormat":1},{"version":"656d1ce5b8fbed896bb803d849d6157242261030967b821d01e72264774cab55","impliedFormat":1},{"version":"da66c1b41d833858fe61947432130d39649f0b53d992dfd7d00f0bbe57191ef4","impliedFormat":1},{"version":"835739c6dcf0a9a1533d1e95b7d7cf8e44ca1341652856b897f4573078b23a31","impliedFormat":1},{"version":"774a3bcc0700036313c57a079e2e1161a506836d736203aa0463efa7b11a7e54","impliedFormat":1},{"version":"96577e3f8e0f9ea07ddf748d72dc1908581ef2aafd4ae7418a4574c26027cf02","impliedFormat":1},{"version":"f55971cb3ede99c17443b03788fe27b259dcd0f890ac31badcb74e3ffb4bb371","impliedFormat":1},{"version":"0ef0c246f8f255a5d798727c40d6d2231d2b0ebda5b1ec75e80eadb02022c548","impliedFormat":1},{"version":"ea127752a5ec75f2ac6ef7f1440634e6ae5bc8d09e6f98b61a8fb600def6a861","impliedFormat":1},{"version":"862320e775649dcca8915f8886865e9c6d8affc1e70ed4b97199f3b70a843b47","impliedFormat":1},{"version":"561764374e9f37cb895263d5c8380885972d75d09d0db64c12e0cb10ba90ae3e","impliedFormat":1},{"version":"ee889da857c29fa7375ad500926748ef2e029a6645d7c080e57769923d15dfef","impliedFormat":1},{"version":"56984ba2d781bd742b6bc0fa34c10df2eae59b42ec8b1b731d297f1590fa4071","impliedFormat":1},{"version":"7521de5e64e2dd022be87fce69d956a52d4425286fbc5697ecfec386da896d7e","impliedFormat":1},{"version":"f50b072ec1f4839b54fd1269a4fa7b03efbc9c59940224c7939632c0f70a39c3","impliedFormat":1},{"version":"a5b7ec6f1ff3f1d19a2547f7e1a50ab1284e6b4755d260a481ea01ed2c7cec60","impliedFormat":1},{"version":"1747f9eebf5beb8cfc46cf0303e300950b7bff20cff60b9c46818caced3226e3","impliedFormat":1},{"version":"9d969f36abb62139a90345ee5d03f1c2479831bd84c8f843d87ec304cad96ead","impliedFormat":1},{"version":"e972b52218fd5919aec6cd0e5e2a5fb75f5d2234cf05597a9441837a382b2b29","impliedFormat":1},{"version":"d1e292b0837d0ef5ede4f52363c9d8e93f5d5234086adc796e11eae390305b36","impliedFormat":1},{"version":"0a9e10028a96865d0f25aeca9e3b1ff0691b9b662aa186d9d490728434cf8261","impliedFormat":1},{"version":"1aed740b674839c89f427f48737bad435ee5a39d80b5929f9dc9cc9ac10a7700","impliedFormat":1},{"version":"6e9e3690dc3a6e99a845482e33ee78915893f2d0d579a55b6a0e9b4c44193371","impliedFormat":1},{"version":"4e7a76cce3b537b6cdb1c4b97e29cb4048ee8e7d829cf3a85f4527e92eb573f2","impliedFormat":1},{"version":"7e7e30f804f94b72d23a606f1d281de404a510984085fea8cbbefc7bdcaf1a37","impliedFormat":1},{"version":"46f1fe93f199a419172d7480407d9572064b54712b69406efa97e0244008b24e","impliedFormat":1},{"version":"044e6aaa3f612833fb80e323c65e9d816c3148b397e93630663cda5c2d8f4de1","impliedFormat":1},{"version":"deaf8eb392c46ea2c88553d3cc38d46cfd5ee498238dbc466e3f5be63ae0f651","impliedFormat":1},{"version":"6a79b61f57699de0a381c8a13f4c4bcd120556bfab0b4576994b6917cb62948b","impliedFormat":1},{"version":"c5133d7bdec65f465df12f0b507fbc0d96c78bfa5a012b0eb322cf1ff654e733","impliedFormat":1},{"version":"7905c052681cbe9286797ec036942618e1e8d698dcc2e60f4fb7a0013d470442","impliedFormat":1},{"version":"89049878a456b5e0870bb50289ea8ece28a2abd0255301a261fa8ab6a3e9a07d","impliedFormat":1},{"version":"d0da4f4fd66f37c13deabc1a641edd629141c333ccf862733788bd27e89436ac","impliedFormat":1},{"version":"d4a4f10062a6d82ba60d3ffde9154ef24b1baf2ce28c6439f5bdfb97aa0d18fc","impliedFormat":1},{"version":"f13310c360ecffddb3858dcb33a7619665369d465f55e7386c31d45dfc3847bf","impliedFormat":1},{"version":"e7bde95a05a0564ee1450bc9a53797b0ac7944bf24d87d6f645baca3aa60df48","impliedFormat":1},{"version":"62e68ce120914431a7d34232d3eca643a7ddd67584387936a5202ae1c4dd9a1b","impliedFormat":1},{"version":"91d695bba902cc2eda7edc076cd17c5c9340f7bb254597deb6679e343effadbb","impliedFormat":1},{"version":"e1cb8168c7e0bd4857a66558fe7fe6c66d08432a0a943c51bacdac83773d5745","impliedFormat":1},{"version":"a464510505f31a356e9833963d89ce39f37a098715fc2863e533255af4410525","impliedFormat":1},{"version":"0612b149cabbc136cb25de9daf062659f306b67793edc5e39755c51c724e2949","impliedFormat":1},{"version":"2579b150b86b5f644d86a6d58f17e3b801772c78866c34d41f86f3fc9eb523fe","impliedFormat":1},{"version":"e4b3a3e1b21a194b29d35488ec880948fc2ef8e937288463ea2981ad62a7b106","impliedFormat":1},{"version":"0353e05b0d8475c10ddd88056e0483b191aa5cdea00a25e0505b96e023f1a2d9","impliedFormat":1},{"version":"6a312caabb43c284a4b0da60d5c24f285338096eb9e977af1faca38d32a34685","impliedFormat":1},{"version":"b6eda93163beb978dd0d3042b11c60373506400c94613c0b40d1c0a9a9f1020e","impliedFormat":1},{"version":"a75a1a96af985b462ad8fe0ff86272e5484e2fb9bf7b436f32d466e3ce2e555b","impliedFormat":1},{"version":"99193bafaa9ce112889698de25c4b8c80b1209bb7402189aea1c7ada708a8a54","impliedFormat":1},{"version":"70473538c6eb9494d53bf1539fe69df68d87c348743d8f7244dcb02ca3619484","impliedFormat":1},{"version":"c48932ab06a4e7531bdca7b0f739ace5fa273f9a1b9009bcd26902f8c0b851f0","impliedFormat":1},{"version":"df6c83e574308f6540c19e3409370482a7d8f448d56c65790b4ac0ab6f6fedd8","impliedFormat":1},{"version":"ebbe6765a836bfa7f03181bc433c8984ca29626270ca1e240c009851222cb8a7","impliedFormat":1},{"version":"20f630766b73752f9d74aab6f4367dba9664e8122ea2edcb00168e4f8b667627","impliedFormat":1},{"version":"468df9d24a6e2bc6b4351417e3b5b4c2ca08264d6d5045fe18eb42e7996e58b4","impliedFormat":1},{"version":"954523d1f4856180cbf79b35bd754e14d3b2aea06c7efd71b254c745976086e9","impliedFormat":1},{"version":"31a030f1225ab463dd0189a11706f0eb413429510a7490192a170114b2af8697","impliedFormat":1},{"version":"6f48f244cd4b5b7e9a0326c74f480b179432397580504726de7c3c65d6304b36","impliedFormat":1},{"version":"5520e6defac8e6cdced6dd28808fafe795cb2cd87407bb1012e13a2b061f50b7","impliedFormat":1},{"version":"c3451661fb058f4e15971bbed29061dd960d02d9f8db1038e08b90d294a05c68","impliedFormat":1},{"version":"1f21aefa51f03629582568f97c20ef138febe32391012828e2a0149c2c393f62","impliedFormat":1},{"version":"b18141cda681d82b2693aef045107a910b90a7409ecff0830e1283f0bb2a53e6","impliedFormat":1},{"version":"18eb53924f27af2a5e9734dce28cf5985df7b2828dade1239241e95b639e9bf1","impliedFormat":1},{"version":"a9f1c52f4e7c2a2c4988b5638bd3dbfe38e408b358d02dd2fb8c8920e877f088","impliedFormat":1},{"version":"a7e10a8ad6536dd0225029e46108b18cee0d3c15c2f6e49bd62798ad85bc57b6","impliedFormat":1},{"version":"8db1ed144dd2304b9bd6e41211e22bad5f4ab1d8006e6ac127b29599f4b36083","impliedFormat":1},{"version":"843a5e3737f2abbbbd43bf2014b70f1c69a80530814a27ae1f8be213ae9ec222","impliedFormat":1},{"version":"6fc1be224ad6b3f3ec11535820def2d21636a47205c2c9de32238ba1ac8d82e6","impliedFormat":1},{"version":"5a44788293f9165116c9c183be66cefef0dc5d718782a04847de53bf664f3cc1","impliedFormat":1},{"version":"afd653ae63ce07075b018ba5ce8f4e977b6055c81cc65998410b904b94003c0a","impliedFormat":1},{"version":"9172155acfeb17b9d75f65b84f36cb3eb0ff3cd763db3f0d1ad5f6d10d55662f","impliedFormat":1},{"version":"71807b208e5f15feffb3ff530bec5b46b1217af0d8cc96dde00d549353bcb864","impliedFormat":1},{"version":"1a6eca5c2bc446481046c01a54553c3ffb856f81607a074f9f0256c59dd0ab13","impliedFormat":1},{"version":"dff93e0997c4e64ff29e9f70cad172c0b438c4f58c119f17a51c94d48164475a","impliedFormat":1},{"version":"fd1ddf926b323dfa439be49c1d41bbe233fe5656975a11183aeb3bf2addfa3bb","impliedFormat":1},{"version":"6dda11db28da6bcc7ff09242cd1866bdddd0ae91e2db3bea03ba66112399641a","impliedFormat":1},{"version":"ea4cd1e72af1aa49cf208b9cb4caf542437beb7a7a5b522f50a5f1b7480362ed","impliedFormat":1},{"version":"903a7d68a222d94da11a5a89449fdd5dd75d83cd95af34c0242e10b85ec33a93","impliedFormat":1},{"version":"e7fe2e7ed5c3a7beff60361632be19a8943e53466b7dd69c34f89faf473206d7","impliedFormat":1},{"version":"b4896cee83379e159f83021e262223354db79e439092e485611163e2082224ff","impliedFormat":1},{"version":"5243e79a643e41d9653011d6c66e95048fc0478eb8593dc079b70877a2e3990e","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","impliedFormat":1},{"version":"f724236417941ea77ec8d38c6b7021f5fb7f8521c7f8c1538e87661f2c6a0774","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"c06b2652ffeb89afd0f1c52c165ced77032f9cd09bc481153fbd6b5504c69494","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"612422d5ba6b4a5c4537f423e9199645468ad80a689801da63ab7edb43f7b835","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"1cbdcdef62bf42688e5d825898c3a59458466c48ad8a4ef3f88d8a1cd04310d6","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc8c6f5322961b56d9906601b20798725df60baeab45ec014fba9f795d5596fd","impliedFormat":1},{"version":"67e435fa530778903f5525904e6645b7368eb5ac622055febd19bb399e328ccd","impliedFormat":1},{"version":"060d305fe4494d8cb2b99d620928d369d1ee55c1645f5e729a2aca07d0f108cb","impliedFormat":1},{"version":"5b22bf96be06fca1e134e3a85351ffe7aa2edf0c9a5bd175b7239ee48186dbf5","impliedFormat":1},{"version":"0c50296ee73dae94efc3f0da4936b1146ca6ce2217acfabb44c19c9a33fa30e5","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"5b48cc670da1dfe926036deaf349085caf822def460a6d50de23f1cc12a5f8cd","impliedFormat":1},{"version":"418f34087d52d001e2688a6282a5de9bf583ff771ed5546af0ae36e4f60a458b","affectsGlobalScope":true,"impliedFormat":1},{"version":"036c9a03b01281f99f8251fdc7088e894566be44b3f139a9d48501a978b611df","impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","impliedFormat":1},{"version":"2c9adcc85574b002c9a6311ff2141055769e0071856ec979d92ff989042b1f1b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1569ad5969ae0de6ad0bdfaaab0dabcd8878bc3147ab096206eed2019bb84e7f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"42a350ce4f6f291198dc89833afcc4fd078248b4e1fdc9d05c846c66a670f710","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"b03754211d839cdc48979996525a32eb62130682c2f9801dfc2f25533ce7238d","impliedFormat":1},{"version":"3910dab597c40e173bf0e0d419d3ce9682c54ebf6ae84849f9b829b1451a17ec","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"486c074a5c0f2254345c0d1c9540380f5463999e42d7e1a159305ea823d3c4b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"f4d99ac08c4097a3cc202bec2c7198c19dcdd80d4ec6c62d1356b10d03b119d5","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"ca77e83162eccec633513723d178d4203bdd5465297267696be08013bab0aa1d","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"f61c153d5abbe4381a1fba0d41be0babdd5de51c626ee7ff14cede14dedc33f1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7803171d745ab18e77f16bf82e742268ff72b7f7382ab191e4277527580c2d89","affectsGlobalScope":true,"impliedFormat":1},{"version":"0e9ba65cd3d6d194bdda7343f9f2917f7144ead96a924ca58d1a14919992dc20","impliedFormat":1},{"version":"255d948f87f24ffd57bcb2fdf95792fd418a2e1f712a98cf2cce88744d75085c","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"0b0dfb2d3e8420ead06f6a11acfb02ddcec42257e6ce391993e895ba0af86de4","affectsGlobalScope":true,"impliedFormat":1},{"version":"bfd3b3c21a56104693183942e221c1896ee23bcb8f8d91ab0b941f7b32985411","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"76e7352249c42b9d54fe1f9e1ebcef777da1cb2eb33038366af49469d433597b","impliedFormat":1},{"version":"88cb622dd0ec1ef860e5c27fa884e60d2eba5ae22c7907dff82c56a69bdd2c8a","impliedFormat":1},{"version":"eb234b3e285e8bc071bdddc1ec0460095e13ead6222d44b02c4e0869522f9ba3","impliedFormat":1},{"version":"c85114872760189e50fef131944427b0fb367f0cc0b6dce164bb427a6fd89381","impliedFormat":1},{"version":"5ad69b0d7e7bdbcd3adfdb6a3e306e935c9c2711b1c60493646504a2f991346e","impliedFormat":1},{"version":"a12a667efdeb03b529bd4ebb4032998ddd32743799f59f9f18b186f8e63a2cf1","impliedFormat":1},{"version":"cee7efa0ae4c58deab218d1df0d1bf84abfd5c356cff28bca1421489cba13a19","impliedFormat":1},{"version":"f9e034b1ae29825c00532e08ea852b0c72885c343ee48d2975db0a6481218ab3","impliedFormat":1},{"version":"1193f49cbb883f40326461fe379e58ffa4c18d15bf6d6a1974ad2894e4fb20f3","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"b6e995b5ef6661f5636ff738e67e4ec90150768ef119ad74b473c404304408a1","impliedFormat":1},{"version":"5d470930bf6142d7cbda81c157869024527dc7911ba55d90b8387ef6e1585aa1","impliedFormat":1},{"version":"074483fdbf20b30bd450e54e6892e96ea093430c313e61be5fdfe51588baa2d6","impliedFormat":1},{"version":"b7e6a6a3495301360edb9e1474702db73d18be7803b3f5c6c05571212acccd16","impliedFormat":1},{"version":"aa7527285c94043f21baf6e337bc60a92c20b6efaa90859473f6476954ac5f79","impliedFormat":1},{"version":"dd3be6d9dcd79e46d192175a756546630f2dc89dab28073823c936557b977f26","impliedFormat":1},{"version":"8d0566152618a1da6536c75a5659c139522d67c63a9ae27e8228d76ab0420584","impliedFormat":1},{"version":"ba06bf784edafe0db0e2bd1f6ecf3465b81f6b1819871bf190a0e0137b5b7f18","impliedFormat":1},{"version":"a0500233cb989bcb78f5f1a81f51eabc06b5c39e3042c560a7489f022f1f55a3","impliedFormat":1},{"version":"220508b3fb6b773f49d8fb0765b04f90ef15caacf0f3d260e3412ed38f71ef09","impliedFormat":1},{"version":"1ad113089ad5c188fec4c9a339cb53d1bcbb65682407d6937557bb23a6e1d4e5","impliedFormat":1},{"version":"e56427c055602078cbf0e58e815960541136388f4fc62554813575508def98b6","impliedFormat":1},{"version":"1f58b0676a80db38df1ce19d15360c20ce9e983b35298a5d0b4aa4eb4fb67e0f","impliedFormat":1},{"version":"3d67e7eb73c6955ee27f1d845cae88923f75c8b0830d4b5440eea2339958e8ec","impliedFormat":1},{"version":"11fec302d58b56033ab07290a3abc29e9908e29d504db9468544b15c4cd7670d","impliedFormat":1},{"version":"c66d6817c931633650edf19a8644eea61aeeb84190c7219911cefa8ddea8bd9a","impliedFormat":1},{"version":"ab1359707e4fc610c5f37f1488063af65cda3badca6b692d44b95e8380e0f6c2","impliedFormat":1},{"version":"37deda160549729287645b3769cf126b0a17e7e2218737352676705a01d5957e","impliedFormat":1},{"version":"d80ffdd55e7f4bc69cde66933582b8592d3736d3b0d1d8cc63995a7b2bcca579","impliedFormat":1},{"version":"c9b71952b2178e8737b63079dba30e1b29872240b122905cbaba756cb60b32f5","impliedFormat":1},{"version":"b596585338b0d870f0e19e6b6bcbf024f76328f2c4f4e59745714e38ee9b0582","impliedFormat":1},{"version":"e6717fc103dfa1635947bf2b41161b5e4f2fabbcaf555754cc1b4340ec4ca587","impliedFormat":1},{"version":"c36186d7bdf1f525b7685ee5bf639e4b157b1e803a70c25f234d4762496f771f","impliedFormat":1},{"version":"026726932a4964341ab8544f12b912c8dfaa388d2936b71cc3eca0cffb49cc1d","impliedFormat":1},{"version":"83188d037c81bd27076218934ba9e1742ddb69cd8cc64cdb8a554078de38eb12","impliedFormat":1},{"version":"7d82f2d6a89f07c46c7e3e9071ab890124f95931d9c999ba8f865fa6ef6cbf72","impliedFormat":1},{"version":"4fc523037d14d9bb6ddb586621a93dd05b6c6d8d59919a40c436ca3ac29d9716","impliedFormat":1},{"version":"cb5eaaa2a079305b1c5344af739b29c479746f7a7aefffc7175d23d8b7c8dbb0","impliedFormat":1},{"version":"bd324dccada40f2c94aaa1ebc82b11ce3927b7a2fe74a5ab92b431d495a86e6f","impliedFormat":1},{"version":"56749bf8b557c4c76181b2fd87e41bde2b67843303ae2eabb299623897d704d6","impliedFormat":1},{"version":"5a6fbec8c8e62c37e9685a91a6ef0f6ecaddb1ee90f7b2c2b71b454b40a0d9a6","impliedFormat":1},{"version":"e7435f2f56c50688250f3b6ef99d8f3a1443f4e3d65b4526dfb31dfd4ba532f8","impliedFormat":1},{"version":"6fc56a681a637069675b2e11b4aa105efe146f7a88876f23537e9ea139297cf9","impliedFormat":1},{"version":"33b7f4106cf45ae7ccbb95acd551e9a5cd3c27f598d48216bda84213b8ae0c7e","impliedFormat":1},{"version":"176d6f604b228f727afb8e96fd6ff78c7ca38102e07acfb86a0034d8f8a2064a","impliedFormat":1},{"version":"1b1a02c54361b8c222392054648a2137fc5983ad5680134a653b1d9f655fe43d","impliedFormat":1},{"version":"8bcb884d06860a129dbffa3500d51116d9d1040bb3bf1c9762eb2f1e7fd5c85c","impliedFormat":1},{"version":"e55c0f31407e1e4eee10994001a4f570e1817897a707655f0bbe4d4a66920e9e","impliedFormat":1},{"version":"a37c2194c586faa8979f50a5c5ca165b0903d31ee62a9fe65e4494aa099712c0","impliedFormat":1},{"version":"6602339ddc9cd7e54261bda0e70fb356d9cdc10e3ec7feb5fa28982f8a4d9e34","impliedFormat":1},{"version":"7ffaa736b8a04b0b8af66092da536f71ef13a5ef0428c7711f32b94b68f7c8c8","impliedFormat":1},{"version":"7b4930d666bbe5d10a19fcc8f60cfa392d3ad3383b7f61e979881d2c251bc895","impliedFormat":1},{"version":"46342f04405a2be3fbfb5e38fe3411325769f14482b8cd48077f2d14b64abcfb","impliedFormat":1},{"version":"8fa675c4f44e6020328cf85fdf25419300f35d591b4f56f56e00f9d52b6fbb3b","impliedFormat":1},{"version":"ba98f23160cfa6b47ee8072b8f54201f21a1ee9addc2ef461ebadf559fe5c43a","impliedFormat":1},{"version":"45a4591b53459e21217dc9803367a651e5a1c30358a015f27de0b3e719db816b","impliedFormat":1},{"version":"9ef22bee37885193b9fae7f4cad9502542c12c7fe16afe61e826cdd822643d84","impliedFormat":1},{"version":"b0451895b894c102eed19d50bd5fcb3afd116097f77a7d83625624fafcca8939","impliedFormat":1},{"version":"bce17120b679ff4f1be70f5fe5c56044e07ed45f1e555db6486c6ded8e1da1c8","impliedFormat":1},{"version":"7590477bfa2e309e677ff7f31cb466f377fcd0e10a72950439c3203175309958","impliedFormat":1},{"version":"3f9ebd554335d2c4c4e7dc67af342d37dc8f2938afa64605d8a93236022cc8a5","impliedFormat":1},{"version":"1c077c9f6c0bc02a36207994a6e92a8fbf72d017c4567f640b52bf32984d2392","impliedFormat":1},{"version":"600b42323925b32902b17563654405968aa12ee39e665f83987b7759224cc317","impliedFormat":1},{"version":"32c8f85f6b4e145537dfe61b94ddd98b47dbdd1d37dc4b7042a8d969cd63a1aa","impliedFormat":1},{"version":"2426ed0e9982c3d734a6896b697adf5ae93d634b73eb15b48da8106634f6d911","impliedFormat":1},{"version":"057431f69d565fb44c246f9f64eac09cf309a9af7afb97e588ebef19cc33c779","impliedFormat":1},{"version":"960d026ca8bf27a8f7a3920ee50438b50ec913d635aa92542ca07558f9c59eca","impliedFormat":1},{"version":"71f5d895cc1a8a935c40c070d3d0fade53ae7e303fd76f443b8b541dee19a90c","impliedFormat":1},{"version":"252eb4750d0439d1674ad0dc30d2a2a3e4655e08ad9e58a7e236b21e78d1d540","impliedFormat":1},{"version":"e344b4a389bb2dfa98f144f3f195387a02b6bdb69deed4a96d16cc283c567778","impliedFormat":1},{"version":"c6cdcd12d577032b84eed1de4d2de2ae343463701a25961b202cff93989439fb","impliedFormat":1},{"version":"3dc633586d48fcd04a4f8acdbf7631b8e4a334632f252d5707e04b299069721e","impliedFormat":1},{"version":"3322858f01c0349ee7968a5ce93a1ca0c154c4692aa8f1721dc5192a9191a168","impliedFormat":1},{"version":"6dde0a77adad4173a49e6de4edd6ef70f5598cbebb5c80d76c111943854636ca","impliedFormat":1},{"version":"09acacae732e3cc67a6415026cfae979ebe900905500147a629837b790a366b3","impliedFormat":1},{"version":"f7b622759e094a3c2e19640e0cb233b21810d2762b3e894ef7f415334125eb22","impliedFormat":1},{"version":"99236ea5c4c583082975823fd19bcce6a44963c5c894e20384bc72e7eccf9b03","impliedFormat":1},{"version":"f6688a02946a3f7490aa9e26d76d1c97a388e42e77388cbab010b69982c86e9e","impliedFormat":1},{"version":"9f642953aba68babd23de41de85d4e97f0c39ef074cb8ab8aa7d55237f62aff6","impliedFormat":1},{"version":"159d95163a0ed369175ae7838fa21a9e9e703de5fdb0f978721293dd403d9f4a","impliedFormat":1},{"version":"2d2ec3235e01474f45a68f28cf826c2f5228b79f7d474d12ca3604cdcfdac80c","impliedFormat":1},{"version":"6dd249868034c0434e170ba6e0451d67a0c98e5a74fd57a7999174ee22a0fa7b","impliedFormat":1},{"version":"9716553c72caf4ff992be810e650707924ec6962f6812bd3fbdb9ac3544fd38f","impliedFormat":1},{"version":"506bc8f4d2d639bebb120e18d3752ddeee11321fd1070ad2ce05612753c628d6","impliedFormat":1},{"version":"053c51bbc32db54be396654ab5ecd03a66118d64102ac9e22e950059bc862a5e","impliedFormat":1},{"version":"1977f62a560f3b0fc824281fd027a97ce06c4b2d47b408f3a439c29f1e9f7e10","impliedFormat":1},{"version":"627570f2487bd8d899dd4f36ecb20fe0eb2f8c379eff297e24caba0c985a6c43","impliedFormat":1},{"version":"0f6e0b1a1deb1ab297103955c8cd3797d18f0f7f7d30048ae73ba7c9fb5a1d89","impliedFormat":1},{"version":"0a051f254f9a16cdde942571baab358018386830fed9bdfff42478e38ba641ce","impliedFormat":1},{"version":"17269f8dfc30c4846ab7d8b5d3c97ac76f50f33de96f996b9bf974d817ed025b","impliedFormat":1},{"version":"9e82194af3a7d314ccbc64bb94bfb62f4bfea047db3422a7f6c5caf2d06540a9","impliedFormat":1},{"version":"083d6f3547ccbf25dfa37b950c50bee6691ed5c42107f038cc324dbca1e173ae","impliedFormat":1},{"version":"952a9eab21103b79b7a6cca8ad970c3872883aa71273f540285cad360c35da40","impliedFormat":1},{"version":"8ba48776335db39e0329018c04486907069f3d7ee06ce8b1a6134b7d745271cc","impliedFormat":1},{"version":"e6d5809e52ed7ef1860d1c483e005d1f71bab36772ef0fd80d5df6db1da0e815","impliedFormat":1},{"version":"893e5cfbae9ed690b75b8b2118b140665e08d182ed8531e1363ec050905e6cb2","impliedFormat":1},{"version":"6ae7c7ada66314a0c3acfbf6f6edf379a12106d8d6a1a15bd35bd803908f2c31","impliedFormat":1},{"version":"e4b1e912737472765e6d2264b8721995f86a463a1225f5e2a27f783ecc013a7b","impliedFormat":1},{"version":"97146bbe9e6b1aab070510a45976faaf37724c747a42d08563aeae7ba0334b4f","impliedFormat":1},{"version":"c40d552bd2a4644b0617ec2f0f1c58618a25d098d2d4aa7c65fb446f3c305b54","impliedFormat":1},{"version":"09e64dea2925f3a0ef972d7c11e7fa75fec4c0824e9383db23eacf17b368532f","impliedFormat":1},{"version":"424ddba00938bb9ae68138f1d03c669f43556fc3e9448ed676866c864ca3f1d6","impliedFormat":1},{"version":"a0fe12181346c8404aab9d9a938360133b770a0c08b75a2fce967d77ca4b543f","impliedFormat":1},{"version":"3cc6eb7935ff45d7628b93bb6aaf1a32e8cb3b24287f9e75694b607484b377b3","impliedFormat":1},{"version":"ced02e78a2e10f89f4d70440d0a8de952a5946623519c54747bc84214d644bac","impliedFormat":1},{"version":"efd463021ccc91579ed8ae62584176baab2cd407c555c69214152480531a2072","impliedFormat":1},{"version":"29647c3b79320cfeecb5862e1f79220e059b26db2be52ea256df9cf9203fb401","impliedFormat":1},{"version":"e8cdefd2dc293cb4866ee8f04368e7001884650bb0f43357c4fe044cc2e1674f","impliedFormat":1},{"version":"582a3578ebba9238eb0c5d30b4d231356d3e8116fea497119920208fb48ccf85","impliedFormat":1},{"version":"185eae4a1e8a54e38f36cd6681cfa54c975a2fc3bc2ba6a39bf8163fac85188d","impliedFormat":1},{"version":"0c0a02625cf59a0c7be595ccc270904042bea523518299b754c705f76d2a6919","impliedFormat":1},{"version":"c44fc1bbdb5d1c8025073cb7c5eab553aa02c069235a1fc4613cd096d578ab80","impliedFormat":1},{"version":"cee72255e129896f0240ceb58c22e207b83d2cc81d8446190d1b4ef9b507ccd6","impliedFormat":1},{"version":"3b54670e11a8d3512f87e46645aa9c83ae93afead4a302299a192ac5458aa586","impliedFormat":1},{"version":"c2fc4d3a130e9dc0e40f7e7d192ef2494a39c37da88b5454c8adf143623e5979","impliedFormat":1},{"version":"2e693158fc1eedba3a5766e032d3620c0e9c8ad0418e4769be8a0f103fdb52cd","impliedFormat":1},{"version":"516275ccf3e66dc391533afd4d326c44dd750345b68bb573fc592e4e4b74545f","impliedFormat":1},{"version":"07c342622568693847f6cb898679402dd19740f815fd43bec996daf24a1e2b85","impliedFormat":1},{"version":"1ca051dd06609ee20d4b9560a8ab04e231b711f562d7c5f7110339258d79e79e","impliedFormat":1},{"version":"e5a8bdf735e7d94e71c28fc84e333405f1a166a86b6d9b34462196c970874700","impliedFormat":1},{"version":"89968316b7069339433bd42d53fe56df98b6990783dfe00c9513fb4bd01c2a1c","impliedFormat":1},{"version":"a4096686f982f6977433ee9759ecbef49da29d7e6a5d8278f0fbc7b9f70fce12","impliedFormat":1},{"version":"62e62a477c56cda719013606616dd856cfdc37c60448d0feb53654860d3113bb","impliedFormat":1},{"version":"207c107dd2bd23fa9febac2fe05c7c72cdac02c3f57003ab2e1c6794a6db0c05","impliedFormat":1},{"version":"55133e906c4ddabecdfcbc6a2efd4536a3ac47a8fa0a3fe6d0b918cac882e0d4","impliedFormat":1},{"version":"2147f8d114cf58c05106c3dccea9924d069c69508b5980ed4011d2b648af2ffe","impliedFormat":1},{"version":"2eb4012a758b9a7ba9121951d7c4b9f103fe2fc626f13bec3e29037bb9420dc6","impliedFormat":1},{"version":"fe61f001bd4bd0a374daa75a2ba6d1bb12c849060a607593a3d9a44e6b1df590","impliedFormat":1},{"version":"cfe8221c909ad721b3da6080570553dea2f0e729afbdbcf2c141252cf22f39b5","impliedFormat":1},{"version":"34e89249b6d840032b9acdec61d136877f84f2cd3e3980355b8a18f119809956","impliedFormat":1},{"version":"6f36ff8f8a898184277e7c6e3bf6126f91c7a8b6a841f5b5e6cb415cfc34820e","impliedFormat":1},{"version":"4b6378c9b1b3a2521316c96f5c777e32a1b14d05b034ccd223499e26de8a379c","impliedFormat":1},{"version":"07be5ae9bf5a51f3d98ffcfacf7de2fe4842a7e5016f741e9fad165bb929be93","impliedFormat":1},{"version":"cb1b37eda1afc730d2909a0f62cac4a256276d5e62fea36db1473981a5a65ab1","impliedFormat":1},{"version":"195f855b39c8a6e50eb1f37d8f794fbd98e41199dffbc98bf629506b6def73d7","impliedFormat":1},{"version":"471386a0a7e4eb88c260bdde4c627e634a772bf22f830c4ec1dad823154fd6f5","impliedFormat":1},{"version":"108314a60f3cb2454f2d889c1fb8b3826795399e5d92e87b2918f14d70c01e69","impliedFormat":1},{"version":"d75cc838286d6b1260f0968557cd5f28495d7341c02ac93989fb5096deddfb47","impliedFormat":1},{"version":"d531dc11bb3a8a577bd9ff83e12638098bfc9e0856b25852b91aac70b0887f2a","impliedFormat":1},{"version":"19968b998a2ab7dfd39de0c942fc738b2b610895843fec25477bc393687babd8","impliedFormat":1},{"version":"c0e6319f0839d76beed6e37b45ec4bb80b394d836db308ae9db4dea0fe8a9297","impliedFormat":1},{"version":"1a7b11be5c442dab3f4af9faf20402798fddf1d3c904f7b310f05d91423ba870","impliedFormat":1},{"version":"079d3f1ddcaf6c0ff28cfc7851b0ce79fcd694b3590afa6b8efa6d1656216924","impliedFormat":1},{"version":"2c817fa37b3d2aa72f01ce4d3f93413a7fbdecafe1b9fb7bd7baaa1bbd46eb08","impliedFormat":1},{"version":"682203aed293a0986cc2fccc6321d862742b48d7359118ac8f36b290d28920d2","impliedFormat":1},{"version":"7406d75a4761b34ce126f099eafe6643b929522e9696e5db5043f4e5c74a9e40","impliedFormat":1},{"version":"7e9c4e62351e3af1e5e49e88ebb1384467c9cd7a03c132a3b96842ccdc8045c4","impliedFormat":1},{"version":"ea1f9c60a912065c08e0876bd9500e8fa194738855effb4c7962f1bfb9b1da86","impliedFormat":1},{"version":"903f34c920e699dacbc483780b45d1f1edcb1ebf4b585a999ece78e403bb2db3","impliedFormat":1},{"version":"100ebfd0470433805c43be5ae377b7a15f56b5d7181c314c21789c4fe9789595","impliedFormat":1},{"version":"12533f60d36d03d3cf48d91dc0b1d585f530e4c9818a4d695f672f2901a74a86","impliedFormat":1},{"version":"21d9968dad7a7f021080167d874b718197a60535418e240389d0b651dd8110e7","impliedFormat":1},{"version":"2ef7349b243bce723d67901991d5ad0dfc534da994af61c7c172a99ff599e135","impliedFormat":1},{"version":"fa103f65225a4b42576ae02d17604b02330aea35b8aaf889a8423d38c18fa253","impliedFormat":1},{"version":"1b9173f64a1eaee88fa0c66ab4af8474e3c9741e0b0bd1d83bfca6f0574b6025","impliedFormat":1},{"version":"1b212f0159d984162b3e567678e377f522d7bee4d02ada1cc770549c51087170","impliedFormat":1},{"version":"46bd71615bdf9bfa8499b9cfce52da03507f7140c93866805d04155fa19caa1b","impliedFormat":1},{"version":"86cb49eb242fe19c5572f58624354ffb8743ff0f4522428ebcabc9d54a837c73","impliedFormat":1},{"version":"fc2fb9f11e930479d03430ee5b6588c3788695372b0ab42599f3ec7e78c0f6d5","impliedFormat":1},{"version":"bb1e5cf70d99c277c9f1fe7a216b527dd6bd2f26b307a8ab65d24248fb3319f5","impliedFormat":1},{"version":"817547eacf93922e22570ba411f23e9164544dead83e379c7ae9c1cfc700c2cf","impliedFormat":1},{"version":"a728478cb11ab09a46e664c0782610d7dd5c9db3f9a249f002c92918ca0308f7","impliedFormat":1},{"version":"9e91ef9c3e057d6d9df8bcbfbba0207e83ef9ab98aa302cf9223e81e32fdfe8d","impliedFormat":1},{"version":"66d30ef7f307f95b3f9c4f97e6c1a5e4c462703de03f2f81aca8a1a2f8739dbd","impliedFormat":1},{"version":"293ca178fd6c23ed33050052c6544c9d630f9d3b11d42c36aa86218472129243","impliedFormat":1},{"version":"90a4be0e17ba5824558c38c93894e7f480b3adf5edd1fe04877ab56c56111595","impliedFormat":1},{"version":"fadd55cddab059940934df39ce2689d37110cfe37cc6775f06b0e8decf3092d7","impliedFormat":1},{"version":"91324fe0902334523537221b6c0bef83901761cfd3bd1f140c9036fa6710fa2b","impliedFormat":1},{"version":"b4f3b4e20e2193179481ab325b8bd0871b986e1e8a8ed2961ce020c2dba7c02d","impliedFormat":1},{"version":"41744c67366a0482db029a21f0df4b52cd6f1c85cbc426b981b83b378ccb6e65","impliedFormat":1},{"version":"c3f3cf7561dd31867635c22f3c47c8491af4cfa3758c53e822a136828fc24e5d","impliedFormat":1},{"version":"a88ddea30fae38aa071a43b43205312dc5ff86f9e21d85ba26b14690dc19d95e","impliedFormat":1},{"version":"b5b2d0510e5455234016bbbaba3839ca21adbc715d1b9c3d6dede7d411a28545","impliedFormat":1},{"version":"5515f17f45c6aafe6459afa3318bba040cb466a8d91617041566808a5fd77a44","impliedFormat":1},{"version":"4df1f0c17953b0450aa988c9930061f8861b114e1649e1a16cfd70c5cbdf8d83","impliedFormat":1},{"version":"441104b363d80fe57eb79a50d495e0b7e3ebeb45a5f0d1a4067d71ef75e8fbfa","impliedFormat":1},"cad3136ccb47a35d66febf64cc5bc72b6ea892be297ecd1afda93576ab758cac",{"version":"c2a6a737189ced24ffe0634e9239b087e4c26378d0490f95141b9b9b042b746c","impliedFormat":1},"c493cc8e9f01f1cb49774f0455feda67c6b3d4a39e7f636a9da9e7c947b6ebdd","04fcf9334953dff2aa6d3e8768629db92ceba3d8bf741f0362b2247a50b4a7b1","2bd70278cdf50f2dd8e3b45a6468a0bec3b4cb5828f89c90e00a1322e9322137","1bfaea1d3914bf6e6fa2299add426e60520e3862187506c13c6adae05317501c","f039969228ddcd39aba11f88ee8b892cab2203b6d50a9d1b0da3f35183dad7a9","63e31069785080f4c5cbbc88a4ac8d40d69d06817ffc4df25cdb74f53f07a743","6fa21c09531e63c74bb6b01b6e8fc0e16fad581aa7335cb79205c5036a211303","f4e20fa85d2f45277c778405da28e98831185811ab3edb53f9cdcdbfec3a03a2","b27dcebf00a0ac9b4d17c7f82be4f90d961151ba0ffab09f2d816ef217f1f11c","4d72be59f32e8815fa9385ecdca22658de021d702b3be71440aae30b338f5b6a",{"version":"ea04fcc91c8bdc7a3dcc831263d8a65274ae35676f13a6218251075c11590c14","signature":"c2dda3a5d8a6f153dc814213a022c0ad936b96a4978001ed07556179244409a4"},{"version":"56ef7a8f39e2c6f29c0a78aa54edd4e1bc2f6709b199ea5b5d3a7c57e6dfaa3d","signature":"233f0d908092b7d62df8ea2bc8be23ccbf89836980fd4f839f13a3bd0c91db6d"},{"version":"4a72074c782113e78a6a4798e84259e49b16843f8129b9e72d86c9bc7e883539","signature":"750d6a4d09393dd08a4553a37a2e1cc297c89ad38851a739c7c06d61045758d9"},{"version":"3bc95de7f859c868dcab72d458450840d11cb2c3084121b2006dabcd11857fc2","signature":"8c578d4ffe70ea292c48fa1de92b256f7241858ac7c7093e45ec6f549505c9f8"},{"version":"a96bcdfe27889cdb283943d899c5b3c96307a439d1c37c54561da229fa78090d","signature":"ea87410683256ba6b30267736feaaf95e373c55e8e5f0a9fe3d31d75f4cb1448"},{"version":"0db188d76b8cf039891606cb3bef4c9ac06b2cbad18b8d5118b2750d06d304a4","signature":"9cf976bde4bbc3168985a8e6798bdf612534645e5a16725c4485f352d4c2b72e"},{"version":"099f463539f3cfc824e861afea17b0bffa13fcb4c74dfb886e577b99a8ae1a21","signature":"b6d6e525d61876058d7410881dcd25b0ae19f67d4e2064b24bb6b3eae0939f2a"},{"version":"49a7b18aa54cadf35139d982b0813a60aedb5661f21de07fb9e96d66598c2973","signature":"5a8a6960c7427e587c0815dbf5f684513715a541f78e46d14d5d6688b2f8af84"},{"version":"c8891e1c7e82d2e9c3f065b48d4420c715fa152973cbc3752410ed6e6c114a9c","signature":"cb7608bf7c122bc1c622ed30fd079ce76114f2129e64b81decd185145577141c"},{"version":"3870e6b3c7d37242ca7a6a3cb9bb36071d7ee350bd3377e576354a670b51eac5","signature":"825739ed6afb4683fe9490ab10194443ae487109b7a3f2b920dcc761e96fb91a"},{"version":"55cd4fc85da00d16a24ad334c9fa8f6f5b872af0d2eb2074009c8d74c47db784","signature":"c6ca52e3a7000fa2d40f02f499cf4be06c54b0287411b9b1c481284cf5b7f195"},{"version":"338d330d3770a7d6cedbd9f8217790647c4e1c64233db530e8d8b621fb716bb1","signature":"da1edf94955cfc14b602006321ebe166238a15e4326c51e8146e9c8aa8c0ad3c"},{"version":"d4296b3861c9c434a464c4e0a586566251af4967cdad19e380be1a72d2a77e53","signature":"13dcc348c0a40e0948093a935a5401ec20a4063fbad245f64e4cc6625f15441d"},"e630aca8c6672334ed1148cd62e1cbbc1453adb24301d3a5c895221820602c36","4cc331ab2cc73c057d4086f55ce026e07ad4ee1866d0d808cd549c37783249d0",{"version":"18a54e5c063757255bcf345a61d0e72b477c36a509d6c9d122a2fcd028f54640","signature":"92092c1d2be57cef91322d30956417d71e8b438996a481532537aa12ce080330"},{"version":"52c272b948d5a16acdca0372196c84a43f79eaa7789a3bb0b4529754f02af071","signature":"0c18f411d62c1501cac94d6e18707e457d60fd5300dfbdf1496f24e8c8af4017"},{"version":"d79d0e259c042e4e380333ff65ede8c1b75661fcbb55ae192058ca5f2f52fa19","signature":"9cbae9865f678278f174ba94938f713de6c377dff60b783555c9ce5f05b7c4eb"},{"version":"84bcc7c6b06f4d643a55dc63b56be0c81d990f8d549b66ea615c553268774dc3","impliedFormat":1},{"version":"2d225e7bda2871c066a7079c88174340950fb604f624f2586d3ea27bb9e5f4ff","impliedFormat":1},{"version":"6a785f84e63234035e511817dd48ada756d984dd8f9344e56eb8b2bdcd8fd001","impliedFormat":1},{"version":"c1422d016f7df2ccd3594c06f2923199acd09898f2c42f50ea8159f1f856f618","impliedFormat":1},{"version":"2973b1b7857ca144251375b97f98474e9847a890331e27132d5a8b3aea9350a8","impliedFormat":1},{"version":"0eb6152d37c84d6119295493dfcc20c331c6fda1304a513d159cdaa599dcb78b","impliedFormat":1},{"version":"237df26f8c326ca00cd9d2deb40214a079749062156386b6d75bdcecc6988a6b","impliedFormat":1},{"version":"cd44995ee13d5d23df17a10213fed7b483fabfd5ea08f267ab52c07ce0b6b4da","impliedFormat":1},{"version":"58ce1486f851942bd2d3056b399079bc9cb978ec933fe9833ea417e33eab676e","impliedFormat":1},{"version":"7557d4d7f19f94341f4413575a3453ba7f6039c9591015bcf4282a8e75414043","impliedFormat":1},{"version":"a3b2cc16f3ce2d882eca44e1066f57a24751545f2a5e4a153d4de31b4cac9bb5","impliedFormat":1},{"version":"ac2b3b377d3068bfb6e1cb8889c99098f2c875955e2325315991882a74d92cc8","impliedFormat":1},{"version":"8deb39d89095469957f73bd194d11f01d9894b8c1f1e27fbf3f6e8122576b336","impliedFormat":1},{"version":"a38a9c41f433b608a0d37e645a31eecf7233ef3d3fffeb626988d3219f80e32f","impliedFormat":1},{"version":"8e1428dcba6a984489863935049893631170a37f9584c0479f06e1a5b1f04332","impliedFormat":1},{"version":"1fce9ecb87a2d3898941c60df617e52e50fb0c03c9b7b2ba8381972448327285","impliedFormat":1},{"version":"5ef0597b8238443908b2c4bf69149ed3894ac0ddd0515ac583d38c7595b151f1","impliedFormat":1},{"version":"ac52b775a80badff5f4ac329c5725a26bd5aaadd57afa7ad9e98b4844767312a","impliedFormat":1},{"version":"6ae5b4a63010c82bf2522b4ecfc29ffe6a8b0c5eea6b2b35120077e9ac54d7a1","impliedFormat":1},{"version":"dd7109c49f416f218915921d44f0f28975df78e04e437c62e1e1eb3be5e18a35","impliedFormat":1},{"version":"eee181112e420b345fc78422a6cc32385ede3d27e2eaf8b8c4ad8b2c29e3e52e","impliedFormat":1},{"version":"25fbe57c8ee3079e2201fe580578fab4f3a78881c98865b7c96233af00bf9624","impliedFormat":1},{"version":"62cc8477858487b4c4de7d7ae5e745a8ce0015c1592f398b63ee05d6e64ca295","impliedFormat":1},{"version":"cc2a9ec3cb10e4c0b8738b02c31798fad312d21ef20b6a2f5be1d077e9f5409d","impliedFormat":1},{"version":"4b4fadcda7d34034737598c07e2dca5d7e1e633cb3ba8dd4d2e6a7782b30b296","impliedFormat":1},{"version":"360fdc8829a51c5428636f1f83e7db36fef6c5a15ed4411b582d00a1c2bd6e97","impliedFormat":1},{"version":"1cf0d15e6ab1ecabbf329b906ae8543e6b8955133b7f6655f04d433e3a0597ab","impliedFormat":1},{"version":"7c9f98fe812643141502b30fb2b5ec56d16aaf94f98580276ae37b7924dd44a4","impliedFormat":1},{"version":"b3547893f24f59d0a644c52f55901b15a3fa1a115bc5ea9a582911469b9348b7","impliedFormat":1},{"version":"596e5b88b6ca8399076afcc22af6e6e0c4700c7cd1f420a78d637c3fb44a885e","impliedFormat":1},{"version":"adddf736e08132c7059ee572b128fdacb1c2650ace80d0f582e93d097ed4fbaf","impliedFormat":1},{"version":"d4cad9dc13e9c5348637170ddd5d95f7ed5fdfc856ddca40234fa55518bc99a6","impliedFormat":1},{"version":"d70675ba7ba7d02e52b7070a369957a70827e4b2bca2c1680c38a832e87b61fd","impliedFormat":1},{"version":"3be71f4ce8988a01e2f5368bdd58e1d60236baf511e4510ee9291c7b3729a27e","impliedFormat":1},{"version":"423d2ccc38e369a7527988d682fafc40267bcd6688a7473e59c5eea20a29b64f","impliedFormat":1},{"version":"2f9fde0868ed030277c678b435f63fcf03d27c04301299580a4017963cc04ce6","impliedFormat":1},{"version":"feeb73d48cc41c6dd23d17473521b0af877751504c30c18dc84267c8eeea429a","impliedFormat":1},{"version":"25f1159094dc0bf3a71313a74e0885426af21c5d6564a254004f2cadf9c5b052","impliedFormat":1},{"version":"cde493e09daad4bb29922fe633f760be9f0e8e2f39cdca999cce3b8690b5e13a","impliedFormat":1},{"version":"3d7f9eb12aface876f7b535cc89dcd416daf77f0b3573333f16ec0a70bcf902a","impliedFormat":1},{"version":"4304f640f7cb4724ea82441accb7c7607fa7207541182470d625adda99b2900b","impliedFormat":1},{"version":"e0205f04611bea8b5b82168065b8ef1476a8e96236201494eb8c785331c43118","impliedFormat":1},{"version":"62d26d8ba4fa15ab425c1b57a050ed76c5b0ecbffaa53f182110aa3a02405a07","impliedFormat":1},{"version":"9941cbf7ca695e95d588f5f1692ab040b078d44a95d231fa9a8f828186b7b77d","impliedFormat":1},{"version":"41b8775befd7ded7245a627e9f4de6110236688ce4c124d2d40c37bc1a3bfe05","impliedFormat":1},{"version":"a6613ee552418429af38391e37389036654a882c342a1b81f2711e8ddac597f2","impliedFormat":1},{"version":"da47cb979ae4a849f9b983f43ef34365b7050c4f5ae2ebf818195858774e1d67","impliedFormat":1},{"version":"ac3bcb82d7280fc313a967f311764258d18caf33db6d2b1a0243cde607ff01a0","impliedFormat":1},{"version":"c9b5632d6665177030428d02603aeac3e920d31ec83ac500b55d44c7da74bd84","impliedFormat":1},{"version":"46456824df16d60f243a7e386562b27bac838aaba66050b9bc0f31e1ab34c1f2","impliedFormat":1},{"version":"b91034069e217212d8dda6c92669ee9f180b4c36273b5244c3be2c657f9286c7","impliedFormat":1},{"version":"0697277dd829ac2610d68fe1b457c9e758105bb52d40e149d9c15e5e2fe6dca4","impliedFormat":1},{"version":"b0d06dbb409369169143ede5df1fb58b2fca8d44588e199bd624b6f6d966bf08","impliedFormat":1},{"version":"88dfdb2a44912a28aea3ebb657dc7fcec6ba59f7233005e3405824995b713dac","impliedFormat":1},{"version":"f38a45cbc0356dfb6455211e75ecd0f8aeaedbacd4cb8d6646c818d36a46d195","impliedFormat":1},{"version":"cc2d5d5687bdf9d7c49b6946b8769ac7abcbdcd1701d9bb9ca70a8bc1b003e8b","impliedFormat":1},{"version":"814e29fd63ffbadaa938eed9ed5b2da3bbdd3671312eaf8d2798010e123aae7a","impliedFormat":1},{"version":"9c0623d67471ddc5b9d82b4e06252c746d54f7ae8ccff8701cd51c249f7e7694","impliedFormat":1},{"version":"71654f68a41362f16a2690ec215e583d3cfa913d49327e43c3c7f354022f67a3","impliedFormat":1},{"version":"f69b484edf398d636992757d587e7e38ea91844a66dbca9d682c9cf7858b77cf","impliedFormat":1},{"version":"37d852b3e6b30b974178674dbf2a7974a1ea4bbdbec26d0bdb8f34632cab94a2","impliedFormat":1},{"version":"c3ccf44723b474b5d7a87c53eed9711e81422f0e29b3e8f8f8d42775fa802243","impliedFormat":1},{"version":"faeaf9586ba4505448bd0fdb332509a7e3ecca5d8ae10208950a5d03e8b4ea28","impliedFormat":1},{"version":"c3a0295707fde0cb51ca784ecb799aa95ca71c40337b5ee4a81a8f7134dd7e92","impliedFormat":1},{"version":"4fe80f12b1d5189384a219095c2eabadbb389c2d3703aae7c5376dbaa56061df","impliedFormat":1},{"version":"9eb1d2dceae65d1c82fc6be7e9b6b19cf3ca93c364678611107362b6ad4d2d41","impliedFormat":1},{"version":"eb81f582c0e1ae21f004b39c268c68ad13222cd7e72f45080d6cdf9b994dfecf","impliedFormat":1},{"version":"e40f9840e4686053a5d42b39fddfd6774172a3d12fa61c1edb8c35b452a5e74a","impliedFormat":1},{"version":"b15c4e40e9927cfb6c6a367553f689e7c29c9543da5889ae6f55a96e9c5e8325","impliedFormat":1},{"version":"479a021fec9328c8ab37f9372dcdc1022d79aeedde6febf03942b4456d1591c9","impliedFormat":1},{"version":"68d25a0614893e256a9e6fe18e2b125dbdad694381b57e63320de6a7518e47fc","impliedFormat":1},{"version":"52d3e951dfbf9550fc5067279bca168b0c3d882a16586c58b0842b4d002ca200","impliedFormat":1},{"version":"4f62dc739ef3b99a93779255f85e51f02ce411d63cf294ee654258ff8c1978e4","impliedFormat":1},{"version":"3fa13ab73c7307a308bba2f9ca5cea92b74cbdae348a7ba1f5ac094fde7286c2","impliedFormat":1},{"version":"64102e00cb41de7f423608037d17dff83954904383e5c45f1054c2246cf5e184","impliedFormat":1},{"version":"69b59327a18869d2168be6beb4ad0bf235fa85c1f3ad9ff1a08de5d26f6c6910","impliedFormat":1},{"version":"05e29a500e59cc5697947ee0fa9390e88ff008ec76be1f859152bda8ec01f13d","impliedFormat":1},{"version":"c814e59aee266fd743b22c3af48e195089fce853f8419663f72502db1cb2c1f7","impliedFormat":1},{"version":"44b227ad122395768f07a8f1b84041b096220335b34ff7af3b8caa61731b294d","impliedFormat":1},{"version":"b0f521f1426905b719680f4b529ddc8c02c5bd88fba8a263d6d0cb2df4c07d36","impliedFormat":1},"20aead67d8719d60a02e3303bb3f64c7c6cfd26667364055276c3f6f3d2c9590","a9db7a55129d4e2e0203d7f7ed651752bfb82bcf749e2a573e8adcfaf652247f","34e21a366b259e630c4019305413a54cbfb384f4fc8553c25edb4b39826502a5",{"version":"fb893a0dfc3c9fb0f9ca93d0648694dd95f33cbad2c0f2c629f842981dfd4e2e","impliedFormat":1},{"version":"95da3c365e3d45709ad6e0b4daa5cdaf05e9076ba3c201e8f8081dd282c02f57","impliedFormat":1},{"version":"7245fa556a63e4c5997642422fe4250df16a56694cc1a1f50108dc80fe3018ba","impliedFormat":1},{"version":"7c14e702387296711c1a829bc95052ff02f533d4aa27d53cc0186c795094a3a9","impliedFormat":1},{"version":"4c72d080623b3dcd8ebd41f38f7ac7804475510449d074ca9044a1cbe95517ae","impliedFormat":1},{"version":"579f8828da42ae02db6915a0223d23b0da07157ff484fecdbf8a96fffa0fa4df","impliedFormat":1},{"version":"5f6beb8c43a52e9119cf31a7583ceca88dc910d7de29caad1f81e98497f808ba","impliedFormat":1},{"version":"3ae3b86c48ae3b092e5d5548acbf4416b427fed498730c227180b5b1a8aa86e3","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"07fcc9be98e12bd2f0f71a501a9bfbe2e53d38c50e8a5e84223fdd05bd8749c5","impliedFormat":1},{"version":"8c724ec27deda8f7ecdca0f22d9bb8c97a4507500ec645f49dea9c73184c2512","impliedFormat":1},{"version":"5b4ca88c4fdda29c9a6580690096c4f30e6d73a8101214ecd672dfcd08fa5dc2","signature":"84ff3f208d4e6c139cc3f55816eed46eaa28fcd995c866b97afd5fcae2db475c"},"8c887dc4d116db093bcbe07d6e6271a26ecdd7f479a12b01b97a05af63da9968","3f9852a684880a15eb0001945e44a6c3dd6203d3e5060f9822baabe95a5027cd",{"version":"c8b70546977da9c426f8bc72e34a5e1e687fe8666f60062a6715a07582a4bf17","signature":"019440849cbe410807bee3e9933b8d9fe5847f8cb4c38b2fe7dbf16ff02c7bb3"},{"version":"04de5584b953b03611eeef01ba9948607def8f64f1e7fbc840752b13b4521b52","impliedFormat":1},{"version":"8b0b6a4c032a56d5651f7dd02ba3f05fbfe4131c4095093633cda3cae0991972","impliedFormat":1},{"version":"192a0c215bffe5e4ac7b9ff1e90e94bf4dfdad4f0f69a5ae07fccc36435ebb87","impliedFormat":1},{"version":"3ef8565e3d254583cced37534f161c31e3a8f341ff005c98b582c6d8c9274538","impliedFormat":1},{"version":"d7e42a3800e287d2a1af8479c7dd58c8663e80a01686cb89e0068be6c777d687","impliedFormat":1},{"version":"1098034333d3eb3c1d974435cacba9bd5a625711453412b3a514774fec7ca748","impliedFormat":1},{"version":"f2388b97b898a93d5a864e85627e3af8638695ebfa6d732ecd39d382824f0e63","impliedFormat":1},{"version":"f130eccfce6e5c42ac92e6d070aa2e41c34312b3a75ce8e2c0dd57e5d3cf1d50","impliedFormat":1},{"version":"f477375e6f0bf2a638a71d4e7a3da8885e3a03f3e5350688541d136b10b762a6","impliedFormat":1},{"version":"a44d6ea4dc70c3d789e9cef3cc42b79c78d17d3ce07f5fd278a7e1cbe824da56","impliedFormat":1},{"version":"55cd8cbc22fe648429a787e16a9cd2dc501a2aafd28c00254ad120ef68a581c0","impliedFormat":1},{"version":"ba4900e9d6f9795a72e8f5ca13c18861821a3fc3ae7858acb0a3366091a47afb","impliedFormat":1},{"version":"7778e2cc5f74ef263a880159aa7fa67254d6232e94dd03429a75597a622537a7","impliedFormat":1},{"version":"8e06a1ef49502a62039eeb927a1bd7561b0bce48bd423a929e2e478fd827c273","impliedFormat":1},{"version":"7ec3d0b061da85d6ff50c337e3248a02a72088462739d88f33b9337dba488c4f","impliedFormat":1},{"version":"2f554c6798b731fc39ff4e3d86aadc932fdeaa063e3cbab025623ff5653c0031","impliedFormat":1},{"version":"fe4613c6c0d23edc04cd8585bdd86bc7337dc6265fb52037d11ca19eeb5e5aaf","impliedFormat":1},{"version":"53b26fbee1a21a6403cf4625d0e501a966b9ccf735754b854366cee8984b711c","impliedFormat":1},{"version":"9ff247206ec5dffdfadddfded2c9d9ad5f714821bb56760be40ed89121f192f4","impliedFormat":1},{"version":"a1a42747e6f2d60162b334556d5355a261e194910e6d7b5f1ad2087668b7964b","impliedFormat":1},{"version":"8c59d8256086ed17676139ee43c1155673e357ab956fb9d00711a7cac73e059d","impliedFormat":1},{"version":"cfe88132f67aa055a3f49d59b01585fa8d890f5a66a0a13bb71973d57573eee7","impliedFormat":1},{"version":"53ce488a97f0b50686ade64252f60a1e491591dd7324f017b86d78239bd232ca","impliedFormat":1},{"version":"50fd11b764194f06977c162c37e5a70bcf0d3579bf82dd4de4eee3ac68d0f82f","impliedFormat":1},{"version":"e0ceb647dcdf6b27fd37e8b0406c7eafb8adfc99414837f3c9bfd28ffed6150a","impliedFormat":1},{"version":"99579aa074ed298e7a3d6a47e68f0cd099e92411212d5081ce88344a5b1b528d","impliedFormat":1},{"version":"096e4ddaa8f0aa8b0ceadd6ab13c3fab53e8a0280678c405160341332eca3cd7","impliedFormat":1},{"version":"415b55892d813a74be51742edd777bbced1f1417848627bf71725171b5325133","impliedFormat":1},{"version":"942ab34f62ac3f3d20014615b6442b6dc51815e30a878ebc390dd70e0dec63bf","impliedFormat":1},{"version":"7a671bf8b4ad81b8b8aea76213ca31b8a5de4ba39490fbdee249fc5ba974a622","impliedFormat":1},{"version":"8e07f13fb0f67e12863b096734f004e14c5ebfd34a524ed4c863c80354c25a44","impliedFormat":1},{"version":"9faa56e38ed5637228530065a9bab19a4dc5a326fbdd1c99e73a310cfed4fcde","impliedFormat":1},{"version":"7d4ad85174f559d8e6ed28a5459aebfc0a7b0872f7775ca147c551e7765e3285","impliedFormat":1},{"version":"d422f0c340060a53cb56d0db24dd170e31e236a808130ab106f7ab2c846f1cdb","impliedFormat":1},{"version":"424403ef35c4c97a7f00ea85f4a5e2f088659c731e75dbe0c546137cb64ef8d8","impliedFormat":1},{"version":"16900e9a60518461d7889be8efeca3fe2cbcd3f6ce6dee70fea81dfbf8990a76","impliedFormat":1},{"version":"6daf17b3bd9499bd0cc1733ab227267d48cd0145ed9967c983ccb8f52eb72d6e","impliedFormat":1},{"version":"e4177e6220d0fef2500432c723dbd2eb9a27dcb491344e6b342be58cc1379ec0","impliedFormat":1},{"version":"ddc62031f48165334486ad1943a1e4ed40c15c94335697cb1e1fd19a182e3102","impliedFormat":1},{"version":"b3f4224eb155d7d13eb377ef40baa1f158f4637aa6de6297dfeeacefd6247476","impliedFormat":1},{"version":"4a168e11fe0f46918721d2f6fcdb676333395736371db1c113ae30b6fde9ccd2","impliedFormat":1},{"version":"5b0a75a5cced0bed0d733bde2da0bbb5d8c8c83d3073444ae52df5f16aefb6ab","impliedFormat":1},{"version":"ef2c1585cad462bdf65f2640e7bcd75cd0dbc45bae297e75072e11fe3db017fa","impliedFormat":1},{"version":"ef809928a4085de826f5b0c84175a56d32dd353856f5b9866d78b8419f8ea9bc","impliedFormat":1},{"version":"6f6eadb32844b0ec7b322293b011316486894f110443197c4c9fbcba01b3b2fa","impliedFormat":1},{"version":"a51e08f41e3e948c287268a275bfe652856a10f68ddd2bf3e3aaf5b8cdb9ef85","impliedFormat":1},{"version":"862f7d760ef37f0ae2c17de82e5fbf336b37d5c1b0dcf39dcd5468f90a7fdd54","impliedFormat":1},{"version":"af48a76b75041e2b3e7bd8eed786c07f39ea896bb2ff165e27e18208d09b8bee","impliedFormat":1},{"version":"cb524ec077f3963e13e85747c6b53fbdf6bf407c84ca1873c6e43da1e96bee6d","impliedFormat":1},{"version":"deb092bc337b2cb0a1b14f3d43f56bc663e1447694e6d479d6df8296bdd452d6","impliedFormat":1},{"version":"041bc1c3620322cb6152183857601707ef6626e9d99f736e8780533689fb1bf9","impliedFormat":1},{"version":"22bd7c75de7d68e075975bf1123de5bccecfd06688afff2e2022b4c70bfc91c3","impliedFormat":1},{"version":"128e7c2ffd37aa29e05367400d718b0e4770cefb1e658d8783ec80a16bc0643a","impliedFormat":1},{"version":"076ac4f2d642c473fa7f01c8c1b7b4ef58f921130174d9cf78430651f44c43ec","impliedFormat":1},{"version":"396c1e5a39706999ec8cc582916e05fcb4f901631d2c192c1292e95089a494d9","impliedFormat":1},{"version":"89df75d28f34fc698fe261f9489125b4e5828fbd62d863bbe93373d3ed995056","impliedFormat":1},{"version":"8ccf5843249a042f4553a308816fe8a03aa423e55544637757d0cfa338bb5186","impliedFormat":1},{"version":"93b44aa4a7b27ba57d9e2bad6fb7943956de85c5cc330d2c3e30cd25b4583d44","impliedFormat":1},{"version":"a0c6216075f54cafdfa90412596b165ff85e2cadd319c49557cc8410f487b77c","impliedFormat":1},{"version":"3c359d811ec0097cba00fb2afd844b125a2ddf4cad88afaf864e88c8d3d358bd","impliedFormat":1},{"version":"3c0b38e8bf11bf3ab87b5116ae8e7b2cad0147b1c80f2b77989dea6f0b93e024","impliedFormat":1},{"version":"8df06e1cd5bb3bf31529cc0db74fa2e57f7de1f6042726679eb8bc1f57083a99","impliedFormat":1},{"version":"d62f09256941e92a95b78ae2267e4cf5ff2ca8915d62b9561b1bc85af1baf428","impliedFormat":1},{"version":"e6223b7263dd7a49f4691bf8df2b1e69f764fb46972937e6f9b28538d050b1ba","impliedFormat":1},{"version":"d9b59eb4e79a0f7a144ee837afb3f1afbc4dab031e49666067a2b5be94b36bd4","impliedFormat":1},{"version":"1db014db736a09668e0c0576585174dbcfd6471bb5e2d79f151a241e0d18d66b","impliedFormat":1},{"version":"8a153d30edde9cefd102e5523b5a9673c298fc7cf7af5173ae946cbb8dd48f11","impliedFormat":1},{"version":"abaaf8d606990f505ee5f76d0b45a44df60886a7d470820fcfb2c06eafa99659","impliedFormat":1},{"version":"51a66bfa412057e786a712733107547ceb6f539061f5bf1c6e5a96e4ccf4f83c","impliedFormat":1},{"version":"d92a80c2c05cf974704088f9da904fe5eadc0b3ad49ddd1ef70ca8028b5adda1","impliedFormat":1},{"version":"fbd7450f20b4486c54f8a90486c395b14f76da66ba30a7d83590e199848f0660","impliedFormat":1},{"version":"ece5b0e45c865645ab65880854899a5422a0b76ada7baa49300c76d38a530ee1","impliedFormat":1},{"version":"62d89ac385aeab821e2d55b4f9a23a277d44f33c67fefe4859c17b80fdb397ea","impliedFormat":1},{"version":"f4dee11887c5564886026263c6ee65c0babc971b2b8848d85c35927af25da827","impliedFormat":1},{"version":"fb8dd49a4cd6d802be4554fbab193bb06e2035905779777f32326cb57cf6a2c2","impliedFormat":1},{"version":"e403ecdfba83013b5eb0e648a92ce182bff2a45ccb81db3035a69081563c2830","impliedFormat":1},{"version":"82d3e00d56a71fc169f3cf9ec5f5ffcc92f6c0e67d4dfc130dafe9f1886d5515","impliedFormat":1},{"version":"b8d57effce2d49a5493debbd8c644e8d52fbe66e2c6d451371375ef5f7bccb8e","impliedFormat":1},{"version":"856024d913cc60b17cf63970f2707ed549c562ccf6ccbb14a171df5a4d1ea44f","impliedFormat":1},{"version":"1b33478647aa1b771314745807397002a410c746480e9447db959110999873ce","impliedFormat":1},{"version":"a14e7c48debe27b25ddf7932e6976c4f58123e32be8384c3f91b0a4d9f67c2f0","impliedFormat":1},{"version":"7e6a96b383da9f5acb848bb9dedb9ac8489df7cec46bbf26aeaed2610f709078","impliedFormat":1},{"version":"9fac6ebf3c60ced53dd21def30a679ec225fc3ff4b8d66b86326c285a4eebb5a","impliedFormat":1},{"version":"8cb83cb98c460cd716d2a98b64eb1a07a3a65c7362436550e02f5c2d212871d1","impliedFormat":1},{"version":"07bc8a3551e39e70c38e7293b1a09916867d728043e352b119f951742cb91624","impliedFormat":1},{"version":"e47adc2176f43c617c0ab47f2d9b2bb1706d9e0669bf349a30c3fe09ddd63261","impliedFormat":1},{"version":"7fec79dfd7319fec7456b1b53134edb54c411ba493a0aef350eee75a4f223eeb","impliedFormat":1},{"version":"189c489705bb96a308dcde9b3336011d08bfbca568bcaf5d5d55c05468e9de7a","impliedFormat":1},{"version":"98f4b1074567341764b580bf14c5aabe82a4390d11553780814f7e932970a6f7","impliedFormat":1},{"version":"1dd24cbf39199100fbe2f3dbd1c7203c240c41d95f66301ecc7650ae77875be1","impliedFormat":1},{"version":"2e252235037a2cd8feebfbf74aa460f783e5d423895d13f29a934d7655a1f8be","impliedFormat":1},{"version":"3bd10a31e9066676e0af937c2ef2507451281861ae294d04c7c46e46706140d9","impliedFormat":1},{"version":"d7fae53b2a4e4f7c7a7f69088e0e5a3baa26f37e03e889ee6089d065ee8f220d","signature":"9bf3cfcb9c196fb79ee3a4c80c23babce310af965f190c2da64efaff0c60dadc"},"efb2a0a1f07689bb1b7c6a9918a8364fd6956ed16fd4052449fc89266d24dc60","499301747cd30b11e47c415d8963e0b3c4d291f2bc76b19914dbcaabf2f11c09",{"version":"5fd43f182d00f9dcd9c58dae2ef94451979a885ea4d88def429ff081f150a7e9","signature":"2aa632b683420832a6b49b00f9a4250302b461c4f9632403354f2b1fea981970"},{"version":"c946df0158e9f138014530d20f00fd772406316c5c5ee8f28f088f3463b290b1","signature":"048becc1beb32a9bd5b9a2411e39e6e3341575ccc6a368991cd00a98950d8b43"},{"version":"f4fbe1ceb29fc45154b02e6f3918651c8417e79c1c061691046881143a267b39","signature":"a28b5c0c372fb375910b3fe3c3ce4331509bc18ccef7cc39c9ee9d8daf8225d1"},{"version":"b11556f91f3cf025c2e1dda14d5aa9f23217e826be1b1510d0687f193f576280","signature":"aa1725e38b5a0b4009f6a74f296f173e7926c119a60454dbd2e523861735df69"},"8843a6c3403a046648adec71bb45aed937ca83b0a4b7b320fad319fae6b2f219",{"version":"51b5ab0d96e014064bebdf91a194df61edec15e1a5c6187ce016799f90afda3a","signature":"1d995d8baebff6cc526b3873d205bcaacf79e6412c8a4b747773569daa893dfe"},{"version":"06bfc9425fdd13a6dc7401b58d474eb31ce395a08a4b285aab4a1c73d8898543","signature":"39b0b28d61904ee2905a55e69bc5dfc7fbebaf69f885973c66d4554ca6341c0c"},"8eac78e8f56d0f3b3680d23f7fc27093c7e687c3a6fbf60b029a1e65960c40c8",{"version":"8ed1e9a44a7773d34354298b0d15c742368075b6e7be024a033af39a713d3155","signature":"aa6f398135df08aaa93c0d1ab6555de38f8e31f83ca769e12c6e682cef62c7b4"},{"version":"56fd7db02c2944a4e45a967dc4ada94643325a9ec3c5d87c256a4ba42416e45b","signature":"dc5863405233aa0fcb2098d6ac5aa001e1dbde50e4f266a204abc5d18c832179"},{"version":"9e054c8b284e028ac0c33b3a2495f2fee6b6005a69a100041cb727c65f5c680a","signature":"a84a0dc1c114c8b2106fff1e5ca20e4434348d9db6547732fd83fa7111ee2e97"},"466a50f4e215dd1632f492dc6bcf961f7c484c3f644658d221524c2b56fffccf",{"version":"44016a5a370a2002ad24f42aed233d5bdfb894cc38952e146f4c5660ef1941ad","signature":"314023eba367e54f7193732fdfcaa0d1756050b8ca6a5323cd27c75109676e13"},"146511fc7bf4972d8d936a73fa61824dd3486c49ac3fe8d8cbb4f5183ddf2b07",{"version":"48db57ab1e7f5f85003a8fdc4a4841f6297bedf155439e1a59cb903e3fb83b6e","signature":"0e4318b32bf665b4c7bff9de9e625bc899b1d1cd1edfa0358831074a4faf2484"},{"version":"fb254f5590a11cc3531e7066ea34225bb997e7abbb4849a92240a392e09f1274","signature":"fcda481b4b1d50ac6dea5dddc997f49a0cfb725871602999b53f29bbe0b4c4f7"},"06e2846b75c20fd5897632574dd19dd80fff6d0ef3741f1c7e014d40043be6a5","6346c5e5386c0fcbd8bb206c65d00eec8dd55b64f77fd12b851c64a137b3a2a3",{"version":"748cddfddfb54fc9efc3b795309174c57805c0247d5e06ba17baf7685ab65e7f","signature":"4c222157e79c909f6db5cff65d88f4a505df8e9ceb99d17bbb71befd2608d3c2"},{"version":"766fc606f90662a77a5b0636793204e36b2aca3174fb7f1d6a766cba88830c42","signature":"302ee7843c35353b8b07391782e1be567ed2d20561324dbbf2bf6480f5208dce"},{"version":"e50baa8db4de60c8f396d7e683339e1b3dd357c95ff052eb854db2c237e6ca5f","signature":"e4f979086a0bf69273fae7333478e4fcd235dbcb031ef384bc2b09f9b1752b6d"},"2422ef36434b385be9aa140fc10f73e12092c227828b46d7525bc14f4a69a029","65c6600302ec11f811d2628e693f361f631f0a877c225a43df862970c2616d08",{"version":"2612e9e6c49ffd6efcc06cced28d33b9df23a99594b2218c5d42349a6cd0fb2d","signature":"5c16cdb8c389cdee9f8455fdd961a0c904eda7988066fb91ff6fef2922a8f655"},{"version":"4ac2fbe2130289a03675183a1eac8e1052401242f8d4cff0a5380c9962aa2cb9","signature":"8a4744bb9a72321c5c39cea3a9d5356f48f08d301737cc47bf119c2144e339ba"},{"version":"301b120a9939ed760eaabf7f934b59fa4b93fe6197615e38495080365166b63b","signature":"7a0d6f87dba454046cd268b23f4fc4883e9679bddbbf65e4a514c57b63d447ad"},"950b76292f19ec7296165bd5761e5b4e225f63417e856efbecc68907266a2b29","a2a5e525c05e789c07f654ec1565b8723b8408426a9f0eaf31d25de99221418c",{"version":"c4d3114dc8e2d15083fcc23a9789bade21cc11a0c32043e04c2afe8f32d757ff","signature":"e81432483a6e0cea07d7d98c275e05ef67f1b896b5ff12a6a8b38644feac8d14"},{"version":"20dd8d241dad7c6827d88002635bf13249b3930065c344ab3eaa8d127a002fa8","signature":"8100528e6b024698e2f0044287e2cc26fe31be0570e4abb214068d2a271fba64"},{"version":"0421daf4494dcb1f8501b192d8733208ac2958e3aa48457a2da768f5f417a871","signature":"7e06b1937efef8222508e57ef8f9baf2a19086a981c61f460347733a588b8f4b"},{"version":"1cf0d9d26738fe0a7512757a86c9d0b964b75650e1b64d22a081ccc16558be8f","signature":"7d5d21d1d545e6e69d4a40ab17564ac8036e5d9371585d37f5ea321c0565973d"},{"version":"5fdb8fb7cf1814c48b1e710a31f457d0bfaad725edcb982af4ed212235566361","signature":"131f94550f3e56e12a2e99ec19a548922ecacb8b974c53beac0e862d4ecae177"},{"version":"edaf75a62a4514b53b2013bd27e34c67e0fb1f5beffbd2dd96b790ff11e8238b","signature":"722edfcf1043c5139c70b730e59c6714873c8b5bf5ff2a3ba25c188f1f2d3a66"},{"version":"bc565b4fd73d4e14040aabca5a1266d2bf7616b7c29fb05798e7c50360984623","signature":"b82491e2990291580288c5602d4c017238977749d52b17391f0e45d9a29be644"},{"version":"41a5fa167b1428ed96d3bc2ccb5ebf1d94d42dc63b3c44e9102fcd6b1f9b0fc4","impliedFormat":1},{"version":"ae2b39d629e335f75d922545bb0ef8a02ef2b7918541c024fc5b0f5cda960585","impliedFormat":1},{"version":"4388c59a46e2f37a43658e773e4993981ecae6fd92e3f081a1d345c70cf3ff0d","impliedFormat":1},{"version":"ff581d4690ec452033a29d3c3843ec5ae4b2f849ad10a60db4beedcbdaa82fff","impliedFormat":1},{"version":"5958ccc3b8918a9a597dfaa0ca8b26c38bc2d848d227c264c26d1aff8f1eb17a","impliedFormat":1},{"version":"f2f5589f5c3f3ea19e96de3761ff5c3b4a5798e7a8b46fe56aa352cc86eaa025","impliedFormat":1},{"version":"0b654da53fb1ea01585221f7327356fc14600968e0312f0930942fef791d0748","impliedFormat":1},{"version":"18c9afd4b6b386520c973adca62c94a92b5e28486af90b40514e9432d82f881e","impliedFormat":1},{"version":"99918776c91e50e7f2d297f128794d04da73ad27b4d4c03337d1ea95950a427a","impliedFormat":1},{"version":"8ca6228625e9eca84fef8f8296e5de49831d31e222fedc045403393ba7951a6a","impliedFormat":1},{"version":"4c03fdceb8ea073f3f9ba15ec4db7db680928da1b430fb53390dcaf9fd96b5dc","impliedFormat":1},{"version":"7536d7105ea97d42602467b5511dc0d1518f20d1c3cd3a16cb6f28d844fa4d63","impliedFormat":1},{"version":"48b3ad35286e3b6431ec14df340eebe7d6924133bc56a64300f632e681e22174","impliedFormat":1},{"version":"6028181bfc0cc383f8e92833e0386bffe5f83a02946ef5fc6dcf046e7055b81a","impliedFormat":1},"1380b63ec8ffce8b4e3a45e7123e948137a51ce662a36255d2cda62e6c49f17a",{"version":"2713c18cff590ef3a615d686d0b9f403c87c36f6cfd737cd10597b66e6bbfe67","signature":"396a3607ca8288c7dd02dff9b04e6ffb057b3c6f3d12965421373c3da78b65cc"},"c1ce6f5ce9889af4a7fcb3c3a2535e2208bff625067dd86115d5c49f3deea34e","7654dd887c547e8e877f5646d5ab774ebb4490c91a24451b2d0a110ec5b9f548","49946f9a0ca49347253df3d2f461e71e43ba24c2da9d2ff8cbb3a10ef9293065",{"version":"da088252b50bb728b9dc0c006a4238c12d7570598b9ebc907bfcd5852959222d","signature":"eb627ce661980d60c154ed1f7495bb1dcc6fc8ad474765e886463e7649e8b8b8"},{"version":"0f915893a258f00fa13a39604488fe804d8a10c122a65fdffd87296355b41892","signature":"74e864f88ad05b669e692a5a270b6f3566ebce1a4dbb9a462486ebf52e26da81"},"4a7e26a3347956974ea600ce8a0e60cc6f1903fcb61c1b67654e9eeead3fcb91",{"version":"a3d3f704c5339a36da3ca8c62b29072f87e86c783b8452d235992142ec71aa2d","impliedFormat":1},{"version":"751764bb94219b4ce8f5475dc35d3de2e432fea01a0c9610cd7f69ad05e398c6","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1}],"root":[712,[714,741],[822,824],[836,839],[932,969],[984,991]],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"emitDecoratorMetadata":true,"esModuleInterop":true,"experimentalDecorators":true,"module":1,"noFallthroughCasesInSwitch":true,"outDir":"./dist","removeComments":true,"rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":9},"referencedMap":[[796,1],[970,2],[797,3],[799,4],[343,3],[330,3],[67,3],[319,5],[320,5],[321,3],[322,6],[332,7],[323,5],[324,8],[325,3],[326,3],[327,5],[328,5],[329,5],[331,9],[339,10],[341,3],[338,3],[345,11],[342,3],[340,3],[336,12],[337,13],[344,14],[346,15],[333,3],[335,16],[334,17],[273,3],[276,18],[272,3],[888,3],[274,3],[275,3],[349,19],[350,19],[351,19],[352,19],[353,19],[354,19],[355,19],[348,20],[356,19],[370,21],[357,19],[347,3],[358,19],[359,19],[360,19],[361,19],[362,19],[363,19],[364,19],[365,19],[366,19],[367,19],[368,19],[369,19],[378,22],[376,23],[375,3],[374,3],[377,24],[418,25],[68,3],[69,3],[70,3],[870,26],[72,27],[876,28],[875,29],[262,30],[263,27],[398,3],[292,3],[293,3],[399,31],[264,3],[400,3],[401,32],[71,3],[266,33],[267,34],[265,35],[268,33],[269,3],[271,36],[283,37],[284,3],[289,38],[285,3],[286,3],[287,3],[288,3],[290,3],[291,39],[297,40],[300,41],[298,3],[299,3],[318,42],[301,3],[302,3],[919,43],[282,44],[280,45],[278,46],[279,47],[281,3],[309,48],[303,3],[312,49],[305,50],[310,51],[308,52],[311,53],[306,54],[307,55],[295,56],[314,57],[296,58],[316,59],[317,60],[304,3],[313,3],[270,3],[277,61],[315,62],[384,63],[379,3],[385,64],[380,65],[381,66],[382,67],[383,68],[386,69],[391,70],[389,71],[390,71],[397,72],[387,3],[388,73],[392,70],[394,74],[396,75],[395,76],[410,77],[403,78],[404,79],[405,79],[406,80],[407,80],[408,79],[409,79],[402,81],[412,82],[411,83],[414,84],[413,85],[415,86],[371,87],[373,88],[294,3],[372,56],[416,89],[393,90],[417,91],[419,6],[539,92],[540,93],[544,94],[420,3],[426,95],[537,96],[538,97],[421,3],[422,3],[425,98],[423,3],[424,3],[542,3],[543,99],[541,100],[545,101],[840,102],[841,103],[861,104],[862,105],[863,3],[864,106],[865,107],[874,108],[867,109],[871,110],[879,111],[877,6],[878,112],[868,113],[880,3],[882,114],[883,115],[884,116],[873,117],[869,118],[893,119],[881,120],[908,121],[866,122],[909,123],[906,124],[907,6],[931,125],[856,126],[852,127],[854,128],[905,129],[847,130],[895,131],[894,3],[855,132],[902,133],[859,134],[903,3],[904,135],[857,136],[858,137],[853,138],[851,139],[846,3],[899,140],[912,141],[910,6],[842,6],[898,142],[843,13],[844,105],[845,143],[849,144],[848,145],[911,146],[850,147],[887,148],[885,114],[886,149],[896,13],[897,150],[900,151],[915,152],[916,153],[913,154],[914,155],[917,156],[918,157],[920,158],[892,159],[889,160],[890,5],[891,149],[922,161],[921,162],[928,163],[860,6],[924,164],[923,6],[926,165],[925,3],[927,166],[872,167],[901,168],[930,169],[929,6],[832,170],[828,171],[827,172],[829,3],[830,173],[831,174],[833,175],[977,176],[978,177],[982,178],[979,6],[980,179],[981,179],[983,180],[971,181],[972,3],[973,182],[976,183],[975,184],[974,185],[992,3],[713,186],[997,187],[996,188],[995,189],[993,3],[994,3],[826,190],[825,3],[481,191],[482,191],[483,192],[429,193],[484,194],[485,195],[486,196],[427,3],[487,197],[488,198],[489,199],[490,200],[491,201],[492,202],[493,202],[494,203],[495,204],[496,205],[497,206],[430,3],[428,3],[498,207],[499,208],[500,209],[535,210],[501,211],[502,3],[503,212],[504,213],[506,214],[507,215],[508,216],[509,217],[510,218],[511,219],[512,220],[513,220],[514,221],[515,3],[516,222],[517,223],[519,224],[518,225],[520,226],[521,227],[522,228],[523,229],[524,230],[525,231],[526,232],[527,233],[528,234],[529,235],[530,236],[531,237],[532,238],[431,3],[432,239],[433,3],[434,3],[477,240],[478,241],[479,3],[480,226],[533,242],[534,243],[615,244],[606,3],[607,3],[608,3],[609,3],[610,3],[611,3],[612,3],[613,3],[614,3],[786,245],[743,3],[745,246],[744,247],[749,248],[784,249],[781,250],[783,251],[746,250],[747,252],[751,252],[750,253],[748,254],[782,255],[795,256],[780,250],[785,257],[778,3],[779,3],[752,258],[757,250],[759,250],[754,250],[755,258],[761,250],[762,259],[753,250],[758,250],[760,250],[756,250],[776,260],[775,250],[777,261],[771,250],[792,262],[790,263],[789,250],[787,248],[794,264],[791,265],[788,263],[793,263],[773,250],[772,250],[768,250],[774,266],[769,250],[770,267],[763,250],[764,250],[765,250],[766,250],[767,250],[835,268],[834,3],[505,3],[563,269],[564,269],[565,269],[571,270],[566,269],[567,269],[568,269],[569,269],[570,269],[554,271],[553,3],[572,272],[560,3],[556,273],[547,3],[546,3],[548,3],[549,269],[550,274],[562,275],[551,269],[552,269],[557,276],[558,277],[559,269],[555,3],[561,3],[576,3],[695,278],[699,278],[698,278],[696,278],[697,278],[700,278],[579,278],[591,278],[580,278],[593,278],[595,278],[589,278],[588,278],[590,278],[594,278],[596,278],[581,278],[592,278],[582,278],[584,279],[585,278],[586,278],[587,278],[603,278],[602,278],[703,280],[597,278],[599,278],[598,278],[600,278],[601,278],[702,278],[701,278],[604,278],[686,278],[685,278],[616,281],[617,281],[619,278],[663,278],[684,278],[620,281],[664,278],[661,278],[665,278],[621,278],[622,278],[623,281],[666,278],[660,281],[618,281],[667,278],[624,281],[668,278],[648,278],[625,281],[626,278],[627,278],[658,281],[630,278],[629,278],[669,278],[670,278],[671,281],[632,278],[634,278],[635,278],[641,278],[642,278],[636,281],[672,278],[659,281],[637,278],[638,278],[673,278],[639,278],[631,281],[674,278],[657,278],[675,278],[640,281],[643,278],[644,278],[662,281],[676,278],[677,278],[656,282],[633,278],[678,281],[679,278],[680,278],[681,278],[682,281],[645,278],[683,278],[649,278],[646,281],[647,281],[628,278],[650,278],[653,278],[651,278],[652,278],[605,278],[693,278],[687,278],[688,278],[690,278],[691,278],[689,278],[694,278],[692,278],[578,283],[711,284],[709,285],[710,286],[708,287],[707,278],[706,288],[575,3],[577,3],[573,3],[704,3],[705,289],[583,283],[574,3],[536,186],[798,290],[742,3],[821,291],[818,292],[816,293],[819,294],[814,295],[813,296],[810,297],[811,298],[812,299],[805,300],[817,301],[815,302],[803,303],[820,304],[804,305],[802,306],[800,307],[655,308],[654,3],[801,309],[806,310],[809,311],[66,3],[261,312],[234,3],[212,313],[210,313],[260,314],[225,315],[224,315],[125,316],[76,317],[232,316],[233,316],[235,318],[236,316],[237,319],[136,320],[238,316],[209,316],[239,316],[240,321],[241,316],[242,315],[243,322],[244,316],[245,316],[246,316],[247,316],[248,315],[249,316],[250,316],[251,316],[252,316],[253,323],[254,316],[255,316],[256,316],[257,316],[258,316],[75,314],[78,319],[79,319],[80,319],[81,319],[82,319],[83,319],[84,319],[85,316],[87,324],[88,319],[86,319],[89,319],[90,319],[91,319],[92,319],[93,319],[94,319],[95,316],[96,319],[97,319],[98,319],[99,319],[100,319],[101,316],[102,319],[103,319],[104,319],[105,319],[106,319],[107,319],[108,316],[110,325],[109,319],[111,319],[112,319],[113,319],[114,319],[115,323],[116,316],[117,316],[131,326],[119,327],[120,319],[121,319],[122,316],[123,319],[124,319],[126,328],[127,319],[128,319],[129,319],[130,319],[132,319],[133,319],[134,319],[135,319],[137,329],[138,319],[139,319],[140,319],[141,316],[142,319],[143,330],[144,330],[145,330],[146,316],[147,319],[148,319],[149,319],[154,319],[150,319],[151,316],[152,319],[153,316],[155,319],[156,319],[157,319],[158,319],[159,319],[160,319],[161,316],[162,319],[163,319],[164,319],[165,319],[166,319],[167,319],[168,319],[169,319],[170,319],[171,319],[172,319],[173,319],[174,319],[175,319],[176,319],[177,319],[178,331],[179,319],[180,319],[181,319],[182,319],[183,319],[184,319],[185,316],[186,316],[187,316],[188,316],[189,316],[190,319],[191,319],[192,319],[193,319],[211,332],[259,316],[196,333],[195,334],[219,335],[218,336],[214,337],[213,336],[215,338],[204,339],[202,340],[217,341],[216,338],[203,3],[205,342],[118,343],[74,344],[73,319],[208,3],[200,345],[201,346],[198,3],[199,347],[197,319],[206,348],[77,349],[226,3],[227,3],[220,3],[223,315],[222,3],[228,3],[229,3],[221,350],[230,3],[231,3],[194,351],[207,352],[807,353],[808,354],[63,3],[64,3],[12,3],[10,3],[11,3],[16,3],[15,3],[2,3],[17,3],[18,3],[19,3],[20,3],[21,3],[22,3],[23,3],[24,3],[3,3],[25,3],[26,3],[4,3],[27,3],[31,3],[28,3],[29,3],[30,3],[32,3],[33,3],[34,3],[5,3],[35,3],[36,3],[37,3],[38,3],[6,3],[42,3],[39,3],[40,3],[41,3],[43,3],[7,3],[44,3],[49,3],[50,3],[45,3],[46,3],[47,3],[48,3],[8,3],[54,3],[51,3],[52,3],[53,3],[55,3],[9,3],[56,3],[65,3],[57,3],[58,3],[60,3],[59,3],[1,3],[61,3],[62,3],[14,3],[13,3],[453,355],[465,356],[450,357],[466,358],[475,359],[441,360],[442,361],[440,362],[474,186],[469,363],[473,364],[444,365],[462,366],[443,367],[472,368],[438,369],[439,363],[445,370],[446,3],[452,371],[449,370],[436,372],[476,373],[467,374],[456,375],[455,370],[457,376],[460,377],[454,378],[458,379],[470,186],[447,380],[448,381],[461,382],[437,358],[464,383],[463,370],[451,381],[459,384],[468,3],[435,3],[471,385],[967,386],[968,387],[966,388],[969,389],[936,390],[935,391],[937,392],[836,393],[933,394],[934,394],[949,395],[950,396],[947,397],[948,394],[823,398],[717,6],[984,179],[932,399],[824,6],[822,185],[986,400],[712,401],[716,402],[715,403],[987,3],[714,404],[951,3],[954,405],[955,406],[953,407],[939,401],[723,6],[940,408],[941,409],[733,410],[728,410],[734,410],[736,411],[735,412],[726,410],[725,3],[731,410],[730,410],[732,410],[729,410],[727,413],[724,3],[740,414],[739,415],[718,416],[719,417],[985,418],[942,419],[944,420],[945,421],[943,422],[988,3],[989,423],[990,424],[720,3],[946,425],[722,426],[952,3],[737,425],[957,3],[738,427],[721,3],[956,394],[959,428],[960,429],[958,430],[838,394],[837,394],[839,431],[938,432],[741,433],[961,394],[962,394],[991,427],[964,434],[965,435],[963,436]],"semanticDiagnosticsPerFile":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997],"affectedFilesPendingEmit":[[967],[968],[966],[969],[936],[935],[937],[836],[949],[950],[947],[932],[954],[955],[953],[941],[739],[985],[944],[945],[943],[989],[990],[959],[960],[958],[839],[938],[741],[964],[965],[963]],"version":"5.8.3"} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0298d53 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2022", + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "test"] +}