diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1deaa2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.vscode +node_modules/ +**/node_modules/ +.pnpm-store/ +.pnp.* +.yarn/ +yarn.lock +*.tsbuildinfo +*.db-shm +*.db-wal +.tmp \ No newline at end of file diff --git a/UndauntedDeployServer/.env.example b/UndauntedDeployServer/.env.example index f3fd483..a558366 100644 --- a/UndauntedDeployServer/.env.example +++ b/UndauntedDeployServer/.env.example @@ -4,4 +4,40 @@ PORT_RANGE_BEGIN= PORT_RANGE_END= GAMESERVER_BINARY_PATH= METAGAME_API_KEY= -SECONDS_TO_WAIT_BETWEEN_GAMESERVER_STARTUP= \ No newline at end of file +SECONDS_TO_WAIT_BETWEEN_GAMESERVER_STARTUP=0 +GAMESERVER_STARTUP_POLL_INTERVAL_MS=100 +GAMESERVER_STARTUP_TIMEOUT_SECONDS=60 +GAMESERVER_PORT_RELEASE_TIMEOUT_MS=5000 +# Show a console window for each gameserver process. File logging remains enabled separately. +GAMESERVER_CONSOLE_LOG=false +# off, report (scan only), safe (force textures/materials collectible), or aggressive +# (also force sounds and inactive-map objects collectible). Cleanup runs once before readiness, +# only after initial load clears and while both tracked and raw connection counts are zero. +GAMESERVER_ASSET_STRIPPING_MODE=aggressive +# Include objects whose outer package belongs to a loaded but non-current map in aggressive mode. +GAMESERVER_STRIP_INACTIVE_MAP_PACKAGES=true +# Write a timestamped asset-strip-*.jsonl candidate manifest beside the game executable. +# Summary cleanup metrics are logged regardless; leave this false to avoid large manifests. +GAMESERVER_ASSET_STRIPPING_LOG_DETAILS=false +# Maximum pre-readiness wait for initial load to clear and both connection counts to remain zero. +# A timeout skips cleanup and publishes readiness; it never retries GC during gameplay. +GAMESERVER_ASSET_GC_WAIT_SECONDS=15 +# Explicit local-development opt-in for native gameserver profiling files. +# Missing or false means no profiling file or writer thread. +GAMESERVER_PROFILING=false +# Native profiling interval in seconds. Invalid values fall back to 30 seconds. +GAMESERVER_PROFILE_INTERVAL_SECONDS=30 +# Maximum age of the reusable actor consideration list; world/level actor changes still rebuild immediately. +GAMESERVER_CONSIDER_CACHE_MAX_AGE_MS=250 +# DeployServer process CPU/memory sampling interval in seconds. +DEPLOYSERVER_PROFILE_INTERVAL_SECONDS=60 +# DeployServer log path. Relative paths resolve from the DeployServer working directory. +DEPLOYSERVER_LOG_FILE=deployserver.log +ADOPT_GAMESERVER=true +# Shut down idle empty ramsgate after this many seconds. +# When both tracked and raw NetDriver connection counts at zero. Set 0 to keep it running. +RAMSGATE_IDLE_SHUTDOWN_SECONDS=10 +PREWARM_TRAINING_DOJO=false +TRAINING_DOJO_IDLE_SHUTDOWN_SECONDS=300 +HUNT_IDLE_SHUTDOWN_SECONDS=180 +ESCALATION_IDLE_SHUTDOWN_SECONDS=30 diff --git a/UndauntedDeployServer/build.cjs b/UndauntedDeployServer/build.cjs new file mode 100644 index 0000000..d223f2f --- /dev/null +++ b/UndauntedDeployServer/build.cjs @@ -0,0 +1,18 @@ +const path = require("path"); +const { build } = require("esbuild"); + +build({ + absWorkingDir: __dirname, + entryPoints: ["./src/server.ts"], + bundle: true, + platform: "node", + target: "es2022", + format: "cjs", + packages: "external", + minify: true, + legalComments: "none", + outfile: "./dist/server.js", +}).catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/UndauntedDeployServer/package-lock.json b/UndauntedDeployServer/package-lock.json deleted file mode 100644 index da53983..0000000 --- a/UndauntedDeployServer/package-lock.json +++ /dev/null @@ -1,3420 +0,0 @@ -{ - "name": "undauntedmetagame", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "undauntedmetagame", - "version": "0.0.1", - "license": "AGPL-3.0-only", - "dependencies": { - "better-sqlite3": "^12.11.1", - "dotenv": "^17.4.2", - "drizzle-orm": "0.45.2", - "express": "5.2.1", - "jsonwebtoken": "^9.0.3", - "pino": "^10.3.1", - "pino-pretty": "^13.1.3" - }, - "devDependencies": { - "@types/express": "5.0.6", - "@types/jsonwebtoken": "^9.0.10", - "@types/node": "26.0.0", - "drizzle-kit": "0.31.10", - "tsx": "4.22.4", - "typescript": "6.0.3" - } - }, - "node_modules/@drizzle-team/brocli": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", - "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "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/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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/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/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", - "peer": true, - "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/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "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/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/drizzle-kit": { - "version": "0.31.10", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", - "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@drizzle-team/brocli": "^0.10.2", - "@esbuild-kit/esm-loader": "^2.5.5", - "esbuild": "^0.25.4", - "tsx": "^4.21.0" - }, - "bin": { - "drizzle-kit": "bin.cjs" - } - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fast-copy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz", - "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==", - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/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/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-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/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "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/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "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/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": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "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/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/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-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/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/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/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/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/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-pretty": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", - "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^4.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "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/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.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/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/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/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/rc/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/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/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/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/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-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==", - "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/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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.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/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/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/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "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/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/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", - "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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/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/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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" - } - } -} diff --git a/UndauntedDeployServer/package.json b/UndauntedDeployServer/package.json index a27d834..6530f3a 100644 --- a/UndauntedDeployServer/package.json +++ b/UndauntedDeployServer/package.json @@ -6,7 +6,7 @@ "author": "gwog :3", "scripts": { "dev": "tsx --env-file=.env src/server.ts", - "build": "tsc", + "build": "node build.cjs", "start": "node --env-file=.env dist/server.js" }, "dependencies": { @@ -23,6 +23,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/node": "26.0.0", "drizzle-kit": "0.31.10", + "esbuild": "0.28.1", "tsx": "4.22.4", "typescript": "6.0.3" } diff --git a/UndauntedDeployServer/src/app.ts b/UndauntedDeployServer/src/app.ts index 58667a3..7347930 100644 --- a/UndauntedDeployServer/src/app.ts +++ b/UndauntedDeployServer/src/app.ts @@ -1,4 +1,5 @@ import express from "express"; +import { gameserverRouter } from "./routes/gameservers.js"; import { matchmakingRouter } from "./routes/matchmaker.js"; import { logger } from "./logger.js"; @@ -8,4 +9,5 @@ app.use(express.json()); app.use(express.urlencoded({ extended: true })); -app.use("/api/matchmaker", matchmakingRouter); \ No newline at end of file +app.use("/api/gameservers", gameserverRouter); +app.use("/api/matchmaker", matchmakingRouter); diff --git a/UndauntedDeployServer/src/controllers/gameservers.ts b/UndauntedDeployServer/src/controllers/gameservers.ts index f1e28d3..d0ff835 100644 --- a/UndauntedDeployServer/src/controllers/gameservers.ts +++ b/UndauntedDeployServer/src/controllers/gameservers.ts @@ -1,4 +1,7 @@ -import { spawn } from "node:child_process" +import { execFileSync, spawn, type ChildProcess } from "node:child_process" +import dgram from "node:dgram"; +import { createReadStream } from "node:fs"; +import { dirname, join } from "node:path"; import { setTimeout } from "node:timers/promises"; import crypto from "node:crypto"; @@ -7,12 +10,20 @@ import PlayerHuntTable from "../vendor/player_hunts_table.json"; import MatchmakerHuntTable from "../vendor/matchmaker_hunts_table.json"; import TrialsHardHuntTable from "../vendor/trials_hard_table.json"; import TrialsEliteHuntTable from "../vendor/trials_elite_table.json"; -import { kill } from "node:process"; import { logger } from "../logger"; +import { kill } from "node:process"; const RAMSGATE_MAP_PATH = "/Game/Maps/ramsgate/ramsgate_01_persistent"; const TRAINING_DOJO_MAP_PATH = "/Game/Maps/islands/dojo/training_dojo_persistent"; const TRIALS_MAP_PATH = "/Game/Maps/islands/arenas/arena_ramsgate_00"; +const GAMESERVER_PORT_RELEASE_TIMEOUT_MS = Number(process.env.GAMESERVER_PORT_RELEASE_TIMEOUT_MS || "5000"); + +class NoFreeHuntPortsError extends Error { + constructor(){ + super("No free ports left!"); + this.name = "NoFreeHuntPortsError"; + } +} export type Gameserver = { id: string, @@ -23,8 +34,23 @@ export type Gameserver = { expectedPlayers: ExpectedPlayer[] | undefined, isRamsgate: boolean, isTrainingDojo: boolean, + origin: GameserverOrigin, + trigger: string, processId: number, - startTime: Date + startTime: Date, + lastTouchedTime: Date, + callbackToken: string | undefined, + lastLifecycleHeartbeatTime: Date | undefined, + reportedLiveConnections: number | undefined, + reportedRawConnections: number | undefined, + hadLiveConnection: boolean, + lifecycleRetirement: LifecycleRetirementState, + shutdownAfterSeconds: number | undefined, + expectedShutdownReason: string | undefined, + optimization: GameserverOptimizationMetrics | undefined, + dllSha256: string | undefined, + profiling: GameserverProfilingStatus | undefined, + pacing: GameserverPacingStatus | undefined }; type ExpectedPlayer = { @@ -32,140 +58,1583 @@ type ExpectedPlayer = { playerHuntId: string }; +type GameserverOptimizationMetrics = { + mode: "off" | "report" | "safe" | "aggressive", + safetyGate: "not_required" | "passed" | "initial_load" | "connections_present" | "timeout" | "signature_mismatch" | "failed", + signatureValid: boolean, + candidates: number, + collected: number, + retained: number, + textures: number, + materials: number, + sounds: number, + mapPackageCandidates: number, + activeMapPackages: number, + inactiveMapPackages: number, + durationMs: number, + workingSetBefore: number, + workingSetAfter: number, + privateBytesBefore: number, + privateBytesAfter: number, + configuredMaxFps: number, + observedMaxFps: number, + capSignatureValid: boolean, + capResolved: boolean, + capApplied: boolean, + capVerified: boolean, + preReadyNetworkingPasses: number, + netServerMaxTickRate: number, + maxNetTickRate: number, + bootstrapMinimumMilliseconds: number, + bootstrapMaximumMilliseconds: number, + considerCacheMaxAgeMilliseconds: number, + profilingEnabled: boolean, + failed: boolean, + error?: string +}; + +type GameserverReadyPayload = { + id: string, + port: number, + pid: number, + ready?: boolean, + error?: string, + optimization?: GameserverOptimizationMetrics, + profiling?: GameserverProfilingStatus, + pacing?: GameserverPacingStatus +}; + +type LifecycleRetirementState = { + protocolVersion: number | undefined, + heartbeatSequence: number | undefined, + observationSequence: number | undefined, + worldEpoch: number | undefined, + zeroHeartbeatStreak: number, + zeroSinceTime: Date | undefined, + armedHeartbeatSequence: number | undefined +}; + +type GameserverHeartbeatPayload = { + id: string, + port: number, + pid: number, + liveConnections: number, + rawConnections: number, + protocolVersion?: number, + heartbeatSequence?: number, + observationSequence?: number, + worldEpoch?: number +}; + +type GameserverPacingStatus = { + state: "disabled" | "observing" | "active" | "fallback" | "unknown", + installFailure: string, + fallbackReason: string, + executableBuildValid: boolean, + signaturesValid: boolean, + hookEnabled: boolean, + highResolutionApiAvailable: boolean, + timerCreated: boolean, + correctionEverActivated: boolean, + observedFrames: number, + cvarMaxFps: number, + cachedMaxFps: number, + virtualMaxFps: number, + rollingMedianCadenceHz: number, + rollingMedianCoarseOvershootMs: number +}; + +type GameserverProfilingStatus = { + enabled: boolean, + started: boolean, + startupWriteSucceeded: boolean, + writerStarted: boolean, + path: string, + startupBytes: number, + startupError: number, + writerError: number, + intervalBytes: number, + intervalWriteFailures: number, + lastIntervalError: number, + dllSha256: string +}; + +type PendingGameserverReady = { + port: number, + token: string, + expectedDllSha256: string, + resolve: (Payload: GameserverReadyPayload) => void, + reject: (Reason: Error) => void +}; + +type StartedGameserverProcess = { + processId: number, + child: ChildProcess | undefined +}; + +export type GameserverOrigin = "RAMSGATE_LAZY" | "TRAINING_DOJO_PREWARM" | "TRAINING_DOJO_LAZY" | "HUNT_ARGS" | "HUNT_MATCHMAKER"; + +type ProcessExitInfo = { + code: number | null, + signal: NodeJS.Signals | null +}; + export let Gameservers: Gameserver[] = []; let FreePorts: number[] = []; +const PendingGameserverReadyById = new Map(); +const PlayerServerAssignments = new Map(); +const PlayerToServerId = new Map(); +const ServerToPlayers = new Map>(); + +export type ShutdownReasonCode = "none" | "idle" | "neverConnected" | "escalation"; +export interface GameserverIdleDecision { + code: ShutdownReasonCode, + serverId: string, + reason: string, + idleSeconds: number, + zeroHeartbeatStreak: number +} + +function RemovePlayerAssignment(PlayerId: string){ + const ServerId = PlayerToServerId.get(PlayerId); + PlayerToServerId.delete(PlayerId); + const Server = PlayerServerAssignments.get(PlayerId); + PlayerServerAssignments.delete(PlayerId); + if(ServerId != undefined){ + const Players = ServerToPlayers.get(ServerId); + Players?.delete(PlayerId); + if(Players != undefined && Players.size === 0) + ServerToPlayers.delete(ServerId); + } + return Server; +} + +function AddPlayerAssignment(PlayerId: string, Server: Gameserver){ + const Previous = PlayerServerAssignments.get(PlayerId); + if(Previous != undefined && Previous !== Server) + ServerToPlayers.get(Previous.id)?.delete(PlayerId); + PlayerServerAssignments.set(PlayerId, Server); + PlayerToServerId.set(PlayerId, Server.id); + let Players = ServerToPlayers.get(Server.id); + if(Players == undefined){ + Players = new Set(); + ServerToPlayers.set(Server.id, Players); + } + Players.add(PlayerId); +} + +let RamsgateServer : Gameserver | undefined; +let TrainingDojoServer : Gameserver | undefined; +let TrainingDojoStartup: Promise | undefined; +let RamsgateStartup: Promise | undefined; +let RamsgateShutdown: Promise | undefined; + +function CreateLifecycleRetirementState(): LifecycleRetirementState { + return { + protocolVersion: undefined, + heartbeatSequence: undefined, + observationSequence: undefined, + worldEpoch: undefined, + zeroHeartbeatStreak: 0, + zeroSinceTime: undefined, + armedHeartbeatSequence: undefined + }; +} + +function CancelLifecycleRetirement(Server: Gameserver, Reason: string){ + const State = Server.lifecycleRetirement; + if(State.zeroHeartbeatStreak > 0 || State.armedHeartbeatSequence != undefined){ + logger.debug({ + serverId: Server.id, + port: Server.port, + reason: Reason, + zeroHeartbeatStreak: State.zeroHeartbeatStreak, + armedHeartbeatSequence: State.armedHeartbeatSequence + }, "Cancelled gameserver idle-retirement evidence"); + } + State.zeroHeartbeatStreak = 0; + State.zeroSinceTime = undefined; + State.armedHeartbeatSequence = undefined; +} + +function TouchGameserver(Server: Gameserver, Now = new Date()){ + Server.lastTouchedTime = Now; + CancelLifecycleRetirement(Server, "activity_touch"); +} + +function AssignPlayersToServer(Server: Gameserver, Players: ExpectedPlayer[]){ + if(Players.length === 0) + return; + + const PlayerIds = new Set(Players.map((Player) => Player.playerUid)); + const PreviousServers = new Set(); + + for(const Player of Players){ + const Previous = PlayerServerAssignments.get(Player.playerUid); + if(Previous != undefined && Previous !== Server){ + PreviousServers.add(Previous); + Previous.expectedPlayers = Previous.expectedPlayers?.filter( + (Existing) => Existing.playerUid !== Player.playerUid); + } + AddPlayerAssignment(Player.playerUid, Server); + } + + const Existing = new Set( + (Server.expectedPlayers ?? []).map((Player) => Player.playerUid)); + Server.expectedPlayers ??= []; + for(const Player of Players){ + if(!Existing.has(Player.playerUid)){ + Server.expectedPlayers.push(Player); + Existing.add(Player.playerUid); + } + } -let RamsgateServer : Gameserver; -let TrainingDojoServer : Gameserver; + logger.info({ + serverId: Server.id, + port: Server.port, + origin: Server.origin, + playerIds: [...PlayerIds], + previousServers: [...PreviousServers].map((Previous) => ({ + id: Previous.id, + port: Previous.port, + processId: Previous.processId + })) + }, "Updated authoritative player gameserver assignments"); +} const PORT_RANGE_BEGIN = Number(process.env.PORT_RANGE_BEGIN!); const PORT_RANGE_END = Number(process.env.PORT_RANGE_END!); const RAMSGATE_PORT = PORT_RANGE_END; const TRAINING_DOJO_PORT = PORT_RANGE_END - 1; -const GAMESERVER_BINARY_PATH = process.env.GAMESERVER_BINARY_PATH!; +const GAMESERVER_BINARY_PATH = process.env.GAMESERVER_BINARY_PATH!.replace(/^"|"$/g, ""); +const GAMESERVER_DLL_PATH = join(dirname(GAMESERVER_BINARY_PATH), "UndauntedInternalServer.dll"); const STANDARD_GAMESERVER_ARGS = ["-EpicPortal", "-server", "-nullrhi"]; const METAGAME_API_KEY = process.env.METAGAME_API_KEY!; const MY_IP = process.env.MY_IP!; +const DEPLOYSERVER_PORT = process.env.PORT!; +const GAMESERVER_READY_CALLBACK_URL = process.env.GAMESERVER_READY_CALLBACK_URL || `http://127.0.0.1:${DEPLOYSERVER_PORT}/api/gameservers/ready`; +const GAMESERVER_LIFECYCLE_CALLBACK_URL = `http://127.0.0.1:${DEPLOYSERVER_PORT}/api/gameservers/heartbeat`; const SECONDS_TO_WAIT_BETWEEN_GAMESERVER_STARTUP = Number(process.env.SECONDS_TO_WAIT_BETWEEN_GAMESERVER_STARTUP!); +const GAMESERVER_STARTUP_TIMEOUT_SECONDS = Number(process.env.GAMESERVER_STARTUP_TIMEOUT_SECONDS || "60"); +const ADOPT_GAMESERVER = process.env.ADOPT_GAMESERVER !== "false"; +const RAMSGATE_IDLE_SHUTDOWN_SECONDS = ParseBoundedIntegerEnvironment( + "RAMSGATE_IDLE_SHUTDOWN_SECONDS", 10, 0, 86400); +const LIFECYCLE_PROTOCOL_VERSION = 2; +const LIFECYCLE_HEARTBEAT_STALE_SECONDS = 7.5; +const LIFECYCLE_MINIMUM_ZERO_HEARTBEATS = 3; +const NEVER_CONNECTED_IDLE_GRACE_SECONDS = 90; +const PREWARM_TRAINING_DOJO = process.env.PREWARM_TRAINING_DOJO === "true"; +const TRAINING_DOJO_IDLE_SHUTDOWN_SECONDS = Number(process.env.TRAINING_DOJO_IDLE_SHUTDOWN_SECONDS || "300"); +const HUNT_IDLE_SHUTDOWN_SECONDS = Number(process.env.HUNT_IDLE_SHUTDOWN_SECONDS || "90"); +const ESCALATION_IDLE_SHUTDOWN_SECONDS = ParseBoundedIntegerEnvironment( + "ESCALATION_IDLE_SHUTDOWN_SECONDS", 30, 0, 86400); +const GAMESERVER_CONSOLE_LOG = process.env.GAMESERVER_CONSOLE_LOG === "true"; +const GAMESERVER_ASSET_STRIPPING_MODE = ParseAssetStrippingMode(process.env.GAMESERVER_ASSET_STRIPPING_MODE); +const GAMESERVER_STRIP_INACTIVE_MAP_PACKAGES = ParseBooleanEnvironment("GAMESERVER_STRIP_INACTIVE_MAP_PACKAGES", true); +const GAMESERVER_ASSET_STRIPPING_LOG_DETAILS = ParseBooleanEnvironment("GAMESERVER_ASSET_STRIPPING_LOG_DETAILS", false); +const GAMESERVER_ASSET_GC_WAIT_SECONDS = ParseBoundedIntegerEnvironment("GAMESERVER_ASSET_GC_WAIT_SECONDS", 15, 1, 15); +const GAMESERVER_PROFILING = ParseBooleanEnvironment("GAMESERVER_PROFILING", false); +const GAMESERVER_PROFILE_INTERVAL_SECONDS = ParseBoundedIntegerEnvironment("GAMESERVER_PROFILE_INTERVAL_SECONDS", 30, 10, 3600); +const GAMESERVER_CONSIDER_CACHE_MAX_AGE_MS = ParseBoundedIntegerEnvironment( + "GAMESERVER_CONSIDER_CACHE_MAX_AGE_MS", 250, 50, 5000); + +function ParseBooleanEnvironment(Name: string, Fallback: boolean){ + const RawValue = process.env[Name]; + + if(RawValue == undefined || RawValue.trim().length === 0){ + return Fallback; + } + + if(RawValue === "true" || RawValue === "1"){ + return true; + } + + if(RawValue === "false" || RawValue === "0"){ + return false; + } + + logger.warn({name: Name, value: RawValue, fallback: Fallback}, "Invalid boolean gameserver configuration; using fallback"); + return Fallback; +} + +function ParseBoundedIntegerEnvironment(Name: string, Fallback: number, Minimum: number, Maximum: number){ + const RawValue = process.env[Name]; + const ParsedValue = Number(RawValue); + + if(RawValue == undefined || RawValue.trim().length === 0 || !Number.isInteger(ParsedValue) || ParsedValue < Minimum || ParsedValue > Maximum){ + if(RawValue != undefined && RawValue.trim().length > 0){ + logger.warn({name: Name, value: RawValue, minimum: Minimum, maximum: Maximum, fallback: Fallback}, "Invalid numeric gameserver configuration; using fallback"); + } + return Fallback; + } + + return ParsedValue; +} + +function ParseAssetStrippingMode(Value: string | undefined): GameserverOptimizationMetrics["mode"]{ + if(Value === "off" || Value === "report" || Value === "safe" || Value === "aggressive"){ + return Value; + } + + if(Value != undefined && Value.trim().length > 0){ + logger.warn({value: Value, fallback: "aggressive"}, "Invalid asset stripping mode; using fallback"); + } + + return "aggressive"; +} + +function IsEscalationServer(HuntId: string | undefined, MatchmakerHuntId: string | undefined, MapPath: string | undefined){ + return [HuntId, MatchmakerHuntId, MapPath] + .some((Value) => Value?.toLowerCase().includes("escalation")); +} + +function GetHuntIdleShutdownSeconds(HuntId: string | undefined, MatchmakerHuntId: string | undefined, MapPath: string | undefined){ + if(IsEscalationServer(HuntId, MatchmakerHuntId, MapPath)){ + return ESCALATION_IDLE_SHUTDOWN_SECONDS; + } + + return HUNT_IDLE_SHUTDOWN_SECONDS; +} + +function FormatHex32(Value: number){ + return `0x${(Value >>> 0).toString(16).toUpperCase().padStart(8, "0")}`; +} + +function ToSigned32(Value: number){ + return Value > 0x7FFFFFFF ? Value - 0x100000000 : Value; +} + +function DescribeWindowsExitCode(Code: number | null){ + if(Code == undefined){ + return "code=null"; + } + + const UnsignedCode = Code >>> 0; + const SignedCode = ToSigned32(UnsignedCode); + const KnownStatus = KnownWindowsExitStatuses.get(UnsignedCode); + const Parts = [ + `code=${Code}`, + `hex=${FormatHex32(UnsignedCode)}` + ]; + + if(SignedCode !== Code){ + Parts.push(`signed=${SignedCode}`); + } + + if(KnownStatus != undefined){ + Parts.push(`status=${KnownStatus}`); + } + + return Parts.join(" "); +} + +function DescribeProcessExit(Exit: ProcessExitInfo){ + return `${DescribeWindowsExitCode(Exit.code)} signal=${Exit.signal ?? "null"}`; +} + +const KnownWindowsExitStatuses = new Map([ + [0xC0000005, "STATUS_ACCESS_VIOLATION"], + [0xC000001D, "STATUS_ILLEGAL_INSTRUCTION"], + [0xC00000FD, "STATUS_STACK_OVERFLOW"], + [0xC0000135, "STATUS_DLL_NOT_FOUND"], + [0xC0000139, "STATUS_ENTRYPOINT_NOT_FOUND"], + [0xC0000142, "STATUS_DLL_INIT_FAILED"], + [0xC0000409, "STATUS_STACK_BUFFER_OVERRUN"], + [0xC0000374, "STATUS_HEAP_CORRUPTION"], + [0xE0434352, "CLR_EXCEPTION"] +]); + +async function IsUdpPortInUse(Port: number){ + return await new Promise((resolve) => { + const Socket = dgram.createSocket("udp4"); + let Settled = false; + + const Finish = (Result: boolean) => { + if(Settled){ + return; + } + + Settled = true; + Socket.removeAllListeners(); + try{ + Socket.close(); + } + catch{ + } + resolve(Result); + }; + + Socket.once("error", (Error: NodeJS.ErrnoException) => { + Finish(Error.code === "EADDRINUSE"); + }); + + Socket.once("listening", () => Finish(false)); + Socket.bind(Port, "0.0.0.0"); + }); +} + +async function WaitForUdpPortRelease(Port: number){ + const Deadline = Date.now() + GAMESERVER_PORT_RELEASE_TIMEOUT_MS; + + while(await IsUdpPortInUse(Port)){ + if(Date.now() >= Deadline){ + throw new Error(`Timed out waiting for gameserver UDP port ${Port} to be released`); + } + + await setTimeout(50); + } +} + +function IsProcessAlive(ProcessId: number){ + try{ + kill(ProcessId, 0); + return true; + } + catch{ + return false; + } +} + +function IsGameserverProcessAlive(GameserverToCheck: Gameserver){ + return IsProcessAlive(GameserverToCheck.processId); +} + +function GetUdpPortOwnerPid(Port: number){ + try{ + const NetstatOutput = execFileSync("netstat", ["-ano"], {encoding: "utf8"}); + const PortPattern = new RegExp(`^\\s*UDP\\s+\\S+:${Port}\\s+\\S+\\s+(\\d+)\\s*$`, "mi"); + const Match = NetstatOutput.match(PortPattern); + + if(Match == undefined){ + return undefined; + } + + return Number(Match[1]); + } + catch(Error){ + logger.warn(Error, `Failed to query owner for UDP port ${Port}`); + return undefined; + } +} + +function CreateAdoptedFixedPortServer(Options: StartServerOptions, Port: number, ProcessId: number): Gameserver{ + const AdoptedServer: Gameserver = { + id: crypto.randomUUID(), + port: Port, + map: Options.map, + behemoth: Options.behemoth, + matchmakerHuntId: Options.matchmakerHuntId, + expectedPlayers: Options.expectedPlayers, + isRamsgate: Options.isRamsgate, + isTrainingDojo: Options.isTrainingDojo, + origin: Options.origin, + trigger: `adopted_existing:${Options.trigger}`, + processId: ProcessId, + startTime: new Date(), + lastTouchedTime: new Date(), + callbackToken: undefined, + lastLifecycleHeartbeatTime: undefined, + reportedLiveConnections: undefined, + reportedRawConnections: undefined, + hadLiveConnection: false, + lifecycleRetirement: CreateLifecycleRetirementState(), + shutdownAfterSeconds: Options.shutdownAfterSeconds, + expectedShutdownReason: undefined, + optimization: undefined, + dllSha256: undefined, + profiling: undefined, + pacing: undefined + }; + + Gameservers.push(AdoptedServer); + for(const Player of AdoptedServer.expectedPlayers ?? []) + AddPlayerAssignment(Player.playerUid, AdoptedServer); + logger.warn(`Adopted existing gameserver process ${ProcessId} on ${MY_IP}:${Port} origin=${Options.origin} map=${Options.map}`); + + return AdoptedServer; +} + +function RegisterPendingGameserverReady(Id: string, Port: number, Token: string, ExpectedDllSha256: string){ + return new Promise((resolve, reject) => { + PendingGameserverReadyById.set(Id, { + port: Port, + token: Token, + expectedDllSha256: ExpectedDllSha256, + resolve: resolve, + reject: reject + }); + }); +} + +function ParseProfilingStatus(Value: unknown): GameserverProfilingStatus | undefined{ + if(typeof Value !== "object" || Value == undefined){ + return undefined; + } + + const Candidate = Value as Record; + const BooleanFields = ["enabled", "started", "startupWriteSucceeded", "writerStarted"] as const; + const NumericFields = [ + "startupBytes", "startupError", "writerError", "intervalBytes", "intervalWriteFailures", "lastIntervalError" + ] as const; + + if(!BooleanFields.every((Field) => typeof Candidate[Field] === "boolean") || + !NumericFields.every((Field) => typeof Candidate[Field] === "number" && + Number.isFinite(Candidate[Field]) && Number(Candidate[Field]) >= 0) || + typeof Candidate.path !== "string" || typeof Candidate.dllSha256 !== "string" || + (Candidate.dllSha256.length !== 0 && !/^[a-fA-F0-9]{64}$/.test(Candidate.dllSha256))){ + return undefined; + } + + return Candidate as GameserverProfilingStatus; +} + +function ParseRequiredProfilingDllSha256(Value: unknown){ + if(typeof Value !== "object" || Value == undefined){ + return undefined; + } + + const DllSha256 = (Value as Record).dllSha256; + if(typeof DllSha256 !== "string" || !/^[a-fA-F0-9]{64}$/.test(DllSha256)){ + return undefined; + } + + return DllSha256.toLowerCase(); +} + +function ParsePacingStatus(Value: unknown): GameserverPacingStatus | undefined{ + if(typeof Value !== "object" || Value == undefined){ + return undefined; + } + + const Candidate = Value as Record; + const States = new Set(["disabled", "observing", "active", "fallback", "unknown"]); + const BooleanFields = [ + "executableBuildValid", "signaturesValid", "hookEnabled", "highResolutionApiAvailable", + "timerCreated", "correctionEverActivated" + ] as const; + const NumericFields = [ + "observedFrames", "cvarMaxFps", "cachedMaxFps", "virtualMaxFps", + "rollingMedianCadenceHz", "rollingMedianCoarseOvershootMs" + ] as const; + + if(!States.has(String(Candidate.state)) || typeof Candidate.installFailure !== "string" || + typeof Candidate.fallbackReason !== "string" || + !BooleanFields.every((Field) => typeof Candidate[Field] === "boolean") || + !NumericFields.every((Field) => typeof Candidate[Field] === "number" && + Number.isFinite(Candidate[Field]) && Number(Candidate[Field]) >= 0)){ + return undefined; + } + + return Candidate as GameserverPacingStatus; +} + +function ParseOptimizationMetrics(Value: unknown): GameserverOptimizationMetrics | undefined{ + if(typeof Value !== "object" || Value == undefined){ + return undefined; + } + + const Candidate = Value as Record; + const Modes = new Set(["off", "report", "safe", "aggressive"]); + const SafetyGates = new Set(["not_required", "passed", "initial_load", "connections_present", "timeout", "signature_mismatch", "failed"]); + const NumericFields = [ + "candidates", "collected", "retained", "textures", "materials", "sounds", + "mapPackageCandidates", "activeMapPackages", "inactiveMapPackages", "durationMs", + "workingSetBefore", "workingSetAfter", "privateBytesBefore", "privateBytesAfter", + "configuredMaxFps", "observedMaxFps", "preReadyNetworkingPasses", + "netServerMaxTickRate", "maxNetTickRate", "bootstrapMinimumMilliseconds", + "bootstrapMaximumMilliseconds", "considerCacheMaxAgeMilliseconds" + ] as const; + + if(!Modes.has(String(Candidate.mode)) || !SafetyGates.has(String(Candidate.safetyGate)) || + typeof Candidate.signatureValid !== "boolean" || typeof Candidate.profilingEnabled !== "boolean" || + typeof Candidate.capSignatureValid !== "boolean" || typeof Candidate.capResolved !== "boolean" || + typeof Candidate.capApplied !== "boolean" || typeof Candidate.capVerified !== "boolean" || + typeof Candidate.failed !== "boolean" || + !NumericFields.every((Field) => typeof Candidate[Field] === "number" && Number.isFinite(Candidate[Field]) && Number(Candidate[Field]) >= 0) || + (Candidate.error != undefined && typeof Candidate.error !== "string")){ + return undefined; + } + + return Candidate as GameserverOptimizationMetrics; +} + +export function HandleGameserverReadyCallback(Token: string | undefined, Body: unknown){ + if(typeof Body !== "object" || Body == undefined){ + return { + status: 400, + body: {ready: false, error: "invalid_body"} + }; + } + + const Payload = Body as Partial; + + if(typeof Payload.id !== "string" || typeof Payload.port !== "number" || typeof Payload.pid !== "number" || + (Payload.ready != undefined && typeof Payload.ready !== "boolean") || + (Payload.error != undefined && typeof Payload.error !== "string")){ + return { + status: 400, + body: {ready: false, error: "invalid_payload"} + }; + } + + const PendingReady = PendingGameserverReadyById.get(Payload.id); + + if(PendingReady == undefined){ + return { + status: 404, + body: {ready: false, error: "unknown_gameserver"} + }; + } + + if(Token !== PendingReady.token){ + return { + status: 403, + body: {ready: false, error: "invalid_token"} + }; + } + + if(Payload.port !== PendingReady.port){ + return { + status: 409, + body: {ready: false, error: "port_mismatch"} + }; + } + + if(Payload.ready === false){ + const FailureReason = Payload.error?.trim() || "unspecified native startup failure"; + PendingReady.reject(new Error(`Gameserver on port ${Payload.port} refused readiness: ${FailureReason}`)); + + return { + status: 200, + body: {ready: false, error: "startup_failed"} + }; + } + + const Optimization = ParseOptimizationMetrics(Payload.optimization); + const Profiling = ParseProfilingStatus(Payload.profiling); + const Pacing = ParsePacingStatus(Payload.pacing); + const ReportedDllSha256 = ParseRequiredProfilingDllSha256(Payload.profiling); + + // Adopted fixed-port processes never register a pending readiness callback, + // so this strict check applies only to a process spawned (and hashed) by this + // DeployServer. Keep the rest of the profiling payload optional: a future + // producer may add fields without making a valid hash unusable. + if(GAMESERVER_PROFILING && ReportedDllSha256 == undefined){ + PendingReady.reject(new Error( + `Gameserver on port ${Payload.port} did not report a valid profiling DLL SHA-256`)); + return { + status: 409, + body: {ready: false, error: "profiling_dll_hash_missing_or_invalid"} + }; + } + + if(GAMESERVER_PROFILING && ReportedDllSha256 !== PendingReady.expectedDllSha256){ + PendingReady.reject(new Error( + `Gameserver on port ${Payload.port} reported DLL SHA-256 ${ReportedDllSha256}; expected ${PendingReady.expectedDllSha256}`)); + return { + status: 409, + body: {ready: false, error: "profiling_dll_hash_mismatch"} + }; + } + + if(Payload.optimization != undefined && Optimization == undefined){ + logger.warn({serverId: Payload.id, port: Payload.port}, "Ignoring invalid optional gameserver optimization metrics"); + } + + if(Payload.profiling != undefined && Profiling == undefined){ + logger.warn({serverId: Payload.id, port: Payload.port}, "Ignoring invalid optional gameserver profiling status"); + } + + if(Payload.pacing != undefined && Pacing == undefined){ + logger.warn({serverId: Payload.id, port: Payload.port}, "Ignoring invalid optional gameserver pacing status"); + } + + PendingReady.resolve({ + id: Payload.id, + port: Payload.port, + pid: Payload.pid, + ready: true, + optimization: Optimization, + profiling: Profiling, + pacing: Pacing + }); + + return { + status: 200, + body: {ready: true} + }; +} + +export function HandleGameserverHeartbeat(Token: string | undefined, Body: unknown){ + if(typeof Body !== "object" || Body == undefined){ + return {status: 400, body: {accepted: false, error: "invalid_body"}}; + } + + const Payload = Body as Partial; + if(typeof Payload.id !== "string" || typeof Payload.port !== "number" + || typeof Payload.pid !== "number" + || typeof Payload.liveConnections !== "number" + || typeof Payload.rawConnections !== "number" + || !Number.isSafeInteger(Payload.port) || !Number.isSafeInteger(Payload.pid) + || !Number.isSafeInteger(Payload.liveConnections) + || Payload.liveConnections < 0 || Payload.liveConnections > 128 + || !Number.isSafeInteger(Payload.rawConnections) + || Payload.rawConnections < 0 || Payload.rawConnections > 128){ + return {status: 400, body: {accepted: false, error: "invalid_payload"}}; + } + + const Server = Gameservers.find((Candidate) => Candidate.id === Payload.id); + if(Server == undefined){ + return {status: 404, body: {accepted: false, error: "unknown_gameserver"}}; + } + if(Token == undefined || Token !== Server.callbackToken){ + return {status: 403, body: {accepted: false, error: "invalid_token"}}; + } + if(Server.port !== Payload.port || Server.processId !== Payload.pid){ + return {status: 409, body: {accepted: false, error: "identity_mismatch"}}; + } + + const LifecycleFields = [Payload.protocolVersion, Payload.heartbeatSequence, + Payload.observationSequence, Payload.worldEpoch]; + const HasAnyLifecycleField = LifecycleFields.some((Value) => Value != undefined); + const HasValidLifecycleFields = + Payload.protocolVersion === LIFECYCLE_PROTOCOL_VERSION + && typeof Payload.heartbeatSequence === "number" + && Number.isSafeInteger(Payload.heartbeatSequence) + && Payload.heartbeatSequence > 0 + && typeof Payload.observationSequence === "number" + && Number.isSafeInteger(Payload.observationSequence) + && Payload.observationSequence > 0 + && typeof Payload.worldEpoch === "number" + && Number.isSafeInteger(Payload.worldEpoch) + && Payload.worldEpoch >= 0 + && Payload.worldEpoch <= 0xffff; + if(HasAnyLifecycleField && !HasValidLifecycleFields){ + CancelLifecycleRetirement(Server, "invalid_lifecycle_fields"); + return {status: 400, body: {accepted: false, error: "invalid_lifecycle_fields"}}; + } + + const Now = new Date(); + const PreviouslyConnected = + (Server.reportedLiveConnections ?? 0) > 0 || + (Server.reportedRawConnections ?? 0) > 0; + Server.lastLifecycleHeartbeatTime = Now; + Server.reportedLiveConnections = Payload.liveConnections; + Server.reportedRawConnections = Payload.rawConnections; + const IsConnected = + Payload.liveConnections > 0 || Payload.rawConnections > 0; + if(IsConnected !== PreviouslyConnected){ + logger.info({ + serverId: Server.id, + port: Server.port, + liveConnections: Payload.liveConnections, + rawConnections: Payload.rawConnections, + lastPositiveConnection: IsConnected + ? Now.toISOString() + : Server.lastTouchedTime.toISOString() + }, IsConnected + ? "Gameserver observed a positive connection" + : "Gameserver observed connection close; zero-evidence window begins"); + } + + const State = Server.lifecycleRetirement; + if(!HasValidLifecycleFields){ + State.protocolVersion = undefined; + State.heartbeatSequence = undefined; + State.observationSequence = undefined; + State.worldEpoch = undefined; + CancelLifecycleRetirement(Server, "legacy_heartbeat"); + if(Payload.liveConnections > 0 || Payload.rawConnections > 0){ + Server.hadLiveConnection = true; + TouchGameserver(Server, Now); + } + return {status: 200, body: {accepted: true, retirementEligible: false}}; + } + + const HeartbeatSequence = Payload.heartbeatSequence!; + const ObservationSequence = Payload.observationSequence!; + const WorldEpoch = Payload.worldEpoch!; + if(State.heartbeatSequence != undefined && + HeartbeatSequence <= State.heartbeatSequence){ + CancelLifecycleRetirement(Server, "heartbeat_sequence_regression"); + return {status: 409, body: {accepted: false, error: "stale_heartbeat"}}; + } + + const ObservationAdvanced = State.observationSequence == undefined + || ObservationSequence > State.observationSequence; + const WorldChanged = State.worldEpoch != undefined && State.worldEpoch !== WorldEpoch; + State.protocolVersion = LIFECYCLE_PROTOCOL_VERSION; + State.heartbeatSequence = HeartbeatSequence; + if(!ObservationAdvanced){ + CancelLifecycleRetirement(Server, "game_thread_observation_not_advanced"); + if(Payload.liveConnections > 0 || Payload.rawConnections > 0){ + Server.hadLiveConnection = true; + TouchGameserver(Server, Now); + } + return {status: 200, body: {accepted: true, retirementEligible: false}}; + } + State.observationSequence = ObservationSequence; + State.worldEpoch = WorldEpoch; + + if(WorldChanged){ + TouchGameserver(Server, Now); + logger.info({serverId: Server.id, port: Server.port, worldEpoch: WorldEpoch}, + "Gameserver lifecycle world changed; reset idle-retirement evidence"); + } + + if(Payload.liveConnections > 0 || Payload.rawConnections > 0){ + Server.hadLiveConnection = true; + TouchGameserver(Server, Now); + } + else { + if(State.zeroHeartbeatStreak === 0){ + State.zeroSinceTime = Now; + logger.debug({serverId: Server.id, port: Server.port, + heartbeatSequence: HeartbeatSequence, observationSequence: ObservationSequence, + worldEpoch: WorldEpoch}, "Gameserver entered zero-connection lifecycle state"); + } + ++State.zeroHeartbeatStreak; + } + + return {status: 200, body: {accepted: true, retirementEligible: true}}; +} + +async function WaitForProcessExit(ProcessId: number, Port: number, ShouldStop: () => boolean): Promise{ + while(!ShouldStop()){ + if(!IsProcessAlive(ProcessId)){ + throw new Error(`Gameserver on port ${Port} stopped during startup: process ${ProcessId} exited`); + } + + await setTimeout(250); + } + + return await new Promise(() => {}); +} + +async function WaitForGameserverReady(StartedProcess: StartedGameserverProcess, Id: string, Port: number, ReadyPromise: Promise){ + let StopPollingProcess = false; + const StartupFailurePromise = StartedProcess.child != undefined + ? new Promise((_, reject) => { + StartedProcess.child!.once("exit", (Code, Signal) => { + reject(new Error(`Gameserver on port ${Port} stopped during startup: ${DescribeProcessExit({code: Code, signal: Signal})}`)); + }); + + StartedProcess.child!.once("error", (SpawnError) => { + reject(new Error(`Gameserver on port ${Port} stopped during startup: spawn error: ${SpawnError.message}`)); + }); + }) + : WaitForProcessExit(StartedProcess.processId, Port, () => StopPollingProcess); + + try{ + return await Promise.race([ + ReadyPromise, + StartupFailurePromise, + setTimeout(GAMESERVER_STARTUP_TIMEOUT_SECONDS * 1000).then(() => { + throw new Error(`Gameserver on port ${Port} did not report ready within ${GAMESERVER_STARTUP_TIMEOUT_SECONDS}s`); + }) + ]); + } + finally{ + StopPollingProcess = true; + PendingGameserverReadyById.delete(Id); + } +} + +function TransformExpectedPlayerArgs(ExpectedPlayers: ExpectedPlayer[]){ + let ToReturn = ""; + + for(const Player of ExpectedPlayers){ + ToReturn = ToReturn + Player.playerUid + ":" + Player.playerHuntId + ","; + } + + if(ToReturn.length > 0){ + ToReturn = ToReturn.slice(0, -1); // Remove trailing ',' + } + + return ToReturn; +} + +async function ComputeGameserverDllSha256(){ + return await new Promise((resolve, reject) => { + const Hash = crypto.createHash("sha256"); + const Stream = createReadStream(GAMESERVER_DLL_PATH); + + Stream.on("data", (Chunk) => Hash.update(Chunk)); + Stream.once("error", reject); + Stream.once("end", () => resolve(Hash.digest("hex"))); + }); +} + +async function StartGameserverProcess(Args: string[]): Promise{ + const Child = spawn(GAMESERVER_BINARY_PATH, Args, { + detached: false, + stdio: "ignore", + windowsHide: !GAMESERVER_CONSOLE_LOG + }); + + return { + processId: Child.pid!, + child: Child + }; +} + +export async function CleanupServer(ServerToShutdown: Gameserver){ + const WasTracked = Gameservers.includes(ServerToShutdown); + Gameservers = Gameservers.filter(Server => Server !== ServerToShutdown); + for(const PlayerId of [...(ServerToPlayers.get(ServerToShutdown.id) ?? [])]) + RemovePlayerAssignment(PlayerId); + ServerToPlayers.delete(ServerToShutdown.id); + + if(!WasTracked && RamsgateServer !== ServerToShutdown && TrainingDojoServer !== ServerToShutdown){ + logger.warn(`Skipping cleanup for already removed gameserver ${ServerToShutdown.processId} on port ${ServerToShutdown.port}`); + return; + } + + if(ServerToShutdown.isRamsgate){ + if(RamsgateServer === ServerToShutdown){ + RamsgateServer = undefined; + } + + if(ServerToShutdown.expectedShutdownReason == undefined){ + logger.warn("Ramsgate stopped unexpectedly; it will lazy-start on the next CITY request"); + } + else{ + logger.info("Ramsgate stopped cleanly and remains dormant until the next CITY request"); + } + } + else if(ServerToShutdown.isTrainingDojo){ + logger.warn("Training Dojo cleaned up; it will lazy-start on the next request"); + + if(TrainingDojoServer === ServerToShutdown){ + TrainingDojoServer = undefined; + } + + TrainingDojoStartup = undefined; + } + else{ + FreePorts.push(ServerToShutdown.port); + } +} -function TransformExpectedPlayerArgs(ExpectedPlayers: ExpectedPlayer[]){ - let ToReturn = ""; +async function CleanupFailedStartup(NewGameserver: Gameserver, Options: StartServerOptions){ + NewGameserver.expectedShutdownReason = "startup_failed"; - for(const Player of ExpectedPlayers){ - ToReturn = ToReturn + Player.playerUid + ":" + Player.playerHuntId + ","; + try{ + kill(NewGameserver.processId); + } + catch(Error){ + logger.warn(Error, `Failed to signal failed gameserver startup ${NewGameserver.processId} on port ${NewGameserver.port}`); } - if(ToReturn.length > 0){ - ToReturn = ToReturn.slice(0, -1); // Remove trailing ',' + Gameservers = Gameservers.filter(Server => Server !== NewGameserver); + + if(Options.isRamsgate){ + if(RamsgateServer === NewGameserver){ + RamsgateServer = undefined; + } + + return; } - return ToReturn; -} + if(Options.isTrainingDojo){ + if(TrainingDojoServer === NewGameserver){ + TrainingDojoServer = undefined; + } -export async function CleanupServer(ServerToShutdown: Gameserver){ - Gameservers = Gameservers.filter(Server => Server !== ServerToShutdown); + TrainingDojoStartup = undefined; + return; + } - if(ServerToShutdown.isRamsgate){ - logger.warn("RAMSGATE HAS FALLEN! Restarting!"); + FreePorts.push(NewGameserver.port); +} - await StartServer(RAMSGATE_MAP_PATH, undefined, undefined, undefined, true, false); +export async function ShutdownServer(ServerToShutdown: Gameserver, Reason: string){ + if(ServerToShutdown.expectedShutdownReason != undefined){ + logger.debug({ + serverId: ServerToShutdown.id, + port: ServerToShutdown.port, + existingReason: ServerToShutdown.expectedShutdownReason, + ignoredReason: Reason + }, "Gameserver shutdown already in progress"); + return; } - else if(ServerToShutdown.isTrainingDojo){ - logger.warn("Training Dojo Crashed! Restarting!"); - await StartServer(TRAINING_DOJO_MAP_PATH, undefined, undefined, undefined, false, true); + logger.info(`Shutting down gameserver ${ServerToShutdown.processId} on port ${ServerToShutdown.port} (${ServerToShutdown.origin}) due to ${Reason}`); + ServerToShutdown.expectedShutdownReason = Reason; + + try{ + kill(ServerToShutdown.processId); } - else{ - FreePorts.push(ServerToShutdown.port); + catch(Error){ + logger.warn(Error, `Failed to signal gameserver ${ServerToShutdown.processId} on port ${ServerToShutdown.port}`); } + + await WaitForUdpPortRelease(ServerToShutdown.port); + + await CleanupServer(ServerToShutdown); } let ServerLaunchQueue: Promise = Promise.resolve(); -async function StartServer(Map: string, Behemoth: string | undefined, MatchmakerHuntId: string | undefined, ExpectedPlayers: ExpectedPlayer[] | undefined, IsRamsgate: boolean, IsTrainingDojo: boolean){ +type StartServerOptions = { + map: string, + behemoth: string | undefined, + matchmakerHuntId: string | undefined, + expectedPlayers: ExpectedPlayer[] | undefined, + isRamsgate: boolean, + isTrainingDojo: boolean, + origin: GameserverOrigin, + trigger: string, + shutdownAfterSeconds: number | undefined +}; + +async function StartServer(Options: StartServerOptions){ const LaunchProc = ServerLaunchQueue; ServerLaunchQueue = ServerLaunchQueue.catch(() => {}).then(async () => await setTimeout(SECONDS_TO_WAIT_BETWEEN_GAMESERVER_STARTUP * 1000)); await LaunchProc; - let Port; + let Port: number | undefined; - if(IsRamsgate){ + if(Options.isRamsgate){ Port = RAMSGATE_PORT; } - else if(IsTrainingDojo){ + else if(Options.isTrainingDojo){ Port = TRAINING_DOJO_PORT; } else{ - Port = FreePorts.pop(); + while(Port == undefined && FreePorts.length > 0){ + const CandidatePort = FreePorts.pop()!; + + if(await IsUdpPortInUse(CandidatePort)){ + logger.warn(`Skipping port ${CandidatePort} because it is already in use before gameserver startup`); + continue; + } + + Port = CandidatePort; + } } const Id = crypto.randomUUID(); if(Port == undefined){ - throw new Error("No free ports left!"); + throw new NoFreeHuntPortsError(); + } + + if((Options.isRamsgate || Options.isTrainingDojo) && await IsUdpPortInUse(Port)){ + const OwnerPid = GetUdpPortOwnerPid(Port); + + if(OwnerPid != undefined){ + if(ADOPT_GAMESERVER){ + return CreateAdoptedFixedPortServer(Options, Port, OwnerPid); + } + + throw new Error(`Fixed gameserver port ${Port} is already owned by pid ${OwnerPid}; refusing to adopt it with ADOPT_GAMESERVER=false`); + } + + throw new Error(`Fixed gameserver port ${Port} is already in use before startup and its owner could not be identified`); + } + + let DllSha256: string; + + try{ + DllSha256 = await ComputeGameserverDllSha256(); + } + catch(CaughtError){ + if(!Options.isRamsgate && !Options.isTrainingDojo){ + FreePorts.push(Port); + } + throw new Error(`Failed to hash gameserver DLL at ${GAMESERVER_DLL_PATH}: ${CaughtError instanceof Error ? CaughtError.message : String(CaughtError)}`); } - const Child = spawn(GAMESERVER_BINARY_PATH, [ + const ReadyCallbackToken = crypto.randomBytes(32).toString("hex"); + const ReadyPromise = RegisterPendingGameserverReady(Id, Port, ReadyCallbackToken, DllSha256); + + const GameserverArgs = [ METAGAME_API_KEY, Port.toString(), - Map, - Behemoth != undefined ? Behemoth : "NO_BEHEMOTH", - MatchmakerHuntId != undefined ? MatchmakerHuntId : "NO_MM_HUNTID", - ExpectedPlayers != undefined ? TransformExpectedPlayerArgs(ExpectedPlayers) : "NO_EXPECTED_PLAYERS", + Options.map, + Options.behemoth != undefined ? Options.behemoth : "NO_BEHEMOTH", + Options.matchmakerHuntId != undefined ? Options.matchmakerHuntId : "NO_MM_HUNTID", + Options.expectedPlayers != undefined ? TransformExpectedPlayerArgs(Options.expectedPlayers) : "NO_EXPECTED_PLAYERS", MY_IP + ":" + Port.toString(), + Id, + GAMESERVER_READY_CALLBACK_URL, + ReadyCallbackToken, + `-undauntedConsoleLog=${GAMESERVER_CONSOLE_LOG}`, + `-undauntedAssetStrippingMode=${GAMESERVER_ASSET_STRIPPING_MODE}`, + `-undauntedStripInactiveMapPackages=${GAMESERVER_STRIP_INACTIVE_MAP_PACKAGES}`, + `-undauntedAssetStrippingLogDetails=${GAMESERVER_ASSET_STRIPPING_LOG_DETAILS}`, + `-undauntedAssetGcWaitSeconds=${GAMESERVER_ASSET_GC_WAIT_SECONDS}`, + `-undauntedProfiling=${GAMESERVER_PROFILING}`, + `-undauntedProfileIntervalSeconds=${GAMESERVER_PROFILE_INTERVAL_SECONDS}`, + `-undauntedConsiderCacheMaxAgeMs=${GAMESERVER_CONSIDER_CACHE_MAX_AGE_MS}`, + `-undauntedLifecycleCallbackUrl=${GAMESERVER_LIFECYCLE_CALLBACK_URL}`, + `-undauntedDllSha256=${DllSha256}`, ...STANDARD_GAMESERVER_ARGS - ]); + ]; + + logger.info({ + id: Id, + port: Port, + origin: Options.origin, + trigger: Options.trigger, + map: Options.map, + dllPath: GAMESERVER_DLL_PATH, + dllSha256: DllSha256, + behemoth: Options.behemoth, + matchmakerHuntId: Options.matchmakerHuntId, + expectedPlayers: Options.expectedPlayers + }, "Starting gameserver process"); - Child.unref(); + const StartedProcess = await StartGameserverProcess(GameserverArgs); + StartedProcess.child?.on("error", (Error) => { + logger.error(Error, `Gameserver process failed to start for port ${Port}`); + }); + let StartupCompleted = false; const NewGameserver: Gameserver = { id: Id, port: Port, - map: Map, - behemoth: Behemoth, - matchmakerHuntId: MatchmakerHuntId, - expectedPlayers: ExpectedPlayers, - isRamsgate: IsRamsgate, - isTrainingDojo: IsTrainingDojo, - processId: Child.pid!, - startTime: new Date() + map: Options.map, + behemoth: Options.behemoth, + matchmakerHuntId: Options.matchmakerHuntId, + expectedPlayers: Options.expectedPlayers, + isRamsgate: Options.isRamsgate, + isTrainingDojo: Options.isTrainingDojo, + origin: Options.origin, + trigger: Options.trigger, + processId: StartedProcess.processId, + startTime: new Date(), + lastTouchedTime: new Date(), + callbackToken: ReadyCallbackToken, + lastLifecycleHeartbeatTime: undefined, + reportedLiveConnections: undefined, + reportedRawConnections: undefined, + hadLiveConnection: false, + lifecycleRetirement: CreateLifecycleRetirementState(), + shutdownAfterSeconds: Options.shutdownAfterSeconds, + expectedShutdownReason: undefined, + optimization: undefined, + dllSha256: DllSha256, + profiling: undefined, + pacing: undefined }; + StartedProcess.child?.on("exit", (Code, Signal) => { + const ExitMessage = `Gameserver process ${StartedProcess.processId} on port ${Port} exited: ${DescribeProcessExit({code: Code, signal: Signal})}`; + + if(NewGameserver.expectedShutdownReason != undefined){ + logger.info(`${ExitMessage} after expected shutdown: ${NewGameserver.expectedShutdownReason}`); + return; + } + + logger.warn(ExitMessage); + + if(!StartupCompleted){ + return; + } + + void CleanupServer(NewGameserver).catch((Error) => { + logger.error(Error, `Failed to cleanup gameserver ${StartedProcess.processId} on port ${Port} after unexpected exit`); + }); + }); + Gameservers.push(NewGameserver); + for(const Player of NewGameserver.expectedPlayers ?? []) + AddPlayerAssignment(Player.playerUid, NewGameserver); + + try{ + const ReadyPayload = await WaitForGameserverReady(StartedProcess, Id, Port, ReadyPromise); + NewGameserver.processId = ReadyPayload.pid; + NewGameserver.optimization = ReadyPayload.optimization; + NewGameserver.profiling = ReadyPayload.profiling; + NewGameserver.pacing = ReadyPayload.pacing; + } + catch(Error){ + await CleanupFailedStartup(NewGameserver, Options); + throw Error; + } + + StartupCompleted = true; + + if(NewGameserver.optimization != undefined){ + logger.info({ + serverId: NewGameserver.id, + port: Port, + optimization: NewGameserver.optimization + }, "Gameserver optimization completed before readiness"); + + } + + logger.info({ + serverId: NewGameserver.id, + port: Port, + expectedDllSha256: DllSha256, + profiling: NewGameserver.profiling, + pacing: NewGameserver.pacing + }, "Gameserver profiling startup status"); + + logger.info(`Gameserver ${NewGameserver.processId} is ready on ${MY_IP}:${Port} origin=${Options.origin} trigger=${Options.trigger} map=${Options.map}`); return NewGameserver; } -export function GetRamsgateConnectionDetails(){ +function StartRamsgateServer(Trigger: string){ + return StartServer({ + map: RAMSGATE_MAP_PATH, + behemoth: undefined, + matchmakerHuntId: undefined, + expectedPlayers: undefined, + isRamsgate: true, + isTrainingDojo: false, + origin: "RAMSGATE_LAZY", + trigger: Trigger, + shutdownAfterSeconds: RAMSGATE_IDLE_SHUTDOWN_SECONDS > 0 + ? RAMSGATE_IDLE_SHUTDOWN_SECONDS : undefined + }); +} + +export async function GetRamsgateConnectionDetails(ExpectedPlayers: string[] = []){ + if(RamsgateShutdown != undefined){ + await RamsgateShutdown; + } + if(RamsgateServer != undefined && !IsGameserverProcessAlive(RamsgateServer)){ + await CleanupServer(RamsgateServer); + } + if(RamsgateServer?.expectedShutdownReason != undefined){ + throw new Error("Ramsgate shutdown is still in progress"); + } + + if(RamsgateServer == undefined){ + if(RamsgateStartup == undefined){ + RamsgateStartup = StartRamsgateServer("city_matchmaking").then((Server) => { + RamsgateServer = Server; + return Server; + }).finally(() => { + RamsgateStartup = undefined; + }); + } + RamsgateServer = await RamsgateStartup; + } + + TouchGameserver(RamsgateServer); + AssignPlayersToServer(RamsgateServer, + TransformExpectedPlayers("ShatteredIsles_ReturnToRamsgate", ExpectedPlayers) ?? []); return { + id: RamsgateServer.id, host: MY_IP, port: RamsgateServer.port }; } +export function GetGameserverIdleShutdownReason(Server: Gameserver, Now = Date.now()): GameserverIdleDecision | undefined{ + if(Server.shutdownAfterSeconds == undefined || Server.shutdownAfterSeconds <= 0 + || Server.expectedShutdownReason != undefined || Server.callbackToken == undefined + || Server.lastLifecycleHeartbeatTime == undefined + || Server.reportedLiveConnections !== 0 || Server.reportedRawConnections !== 0){ + return undefined; + } + + const State = Server.lifecycleRetirement; + if(State.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION + || State.heartbeatSequence == undefined + || State.observationSequence == undefined + || State.worldEpoch == undefined + || State.zeroHeartbeatStreak < LIFECYCLE_MINIMUM_ZERO_HEARTBEATS + || State.zeroSinceTime == undefined){ + return undefined; + } + + const HeartbeatAgeSeconds = + (Now - Server.lastLifecycleHeartbeatTime.getTime()) / 1000; + if(HeartbeatAgeSeconds > LIFECYCLE_HEARTBEAT_STALE_SECONDS){ + CancelLifecycleRetirement(Server, "heartbeat_stale"); + return undefined; + } + + const IdleSeconds = (Now - Server.lastTouchedTime.getTime()) / 1000; + const RequiredIdleSeconds = Server.hadLiveConnection + ? Server.shutdownAfterSeconds + : Math.max(NEVER_CONNECTED_IDLE_GRACE_SECONDS, Server.shutdownAfterSeconds); + if(IdleSeconds < RequiredIdleSeconds){ + return undefined; + } + + if(State.armedHeartbeatSequence == undefined){ + State.armedHeartbeatSequence = State.heartbeatSequence; + logger.info({ + serverId: Server.id, + port: Server.port, + heartbeatSequence: State.heartbeatSequence, + observationSequence: State.observationSequence, + worldEpoch: State.worldEpoch, + zeroHeartbeatStreak: State.zeroHeartbeatStreak, + idleSeconds: Math.floor(IdleSeconds) + }, "Armed gameserver idle retirement; waiting for a confirming heartbeat"); + return undefined; + } + + if(State.heartbeatSequence <= State.armedHeartbeatSequence){ + return undefined; + } + + const Code: ShutdownReasonCode = Server.hadLiveConnection ? "idle" : "neverConnected"; + return { + code: Code, + serverId: Server.id, + idleSeconds: Math.floor(IdleSeconds), + zeroHeartbeatStreak: State.zeroHeartbeatStreak, + reason: `${Code} for ${Math.floor(IdleSeconds)}s with ${State.zeroHeartbeatStreak} fresh zero-connection heartbeats` + }; +} + +export function GetRamsgateIdleShutdownReason(Server: Gameserver, Now = Date.now()){ + return Server.isRamsgate ? GetGameserverIdleShutdownReason(Server, Now) : undefined; +} + +export async function ShutdownIdleGameserver(Server: Gameserver, ExpectedDecision: GameserverIdleDecision){ + if(!Gameservers.includes(Server) || Server.expectedShutdownReason != undefined + || !IsProcessAlive(Server.processId)){ + return; + } + + const RevalidatedReason = GetGameserverIdleShutdownReason(Server); + if(RevalidatedReason == undefined){ + logger.info({serverId: Server.id, port: Server.port, expectedReason: ExpectedDecision.reason}, + "Cancelled gameserver idle retirement during final revalidation"); + return; + } + + await ShutdownServer(Server, RevalidatedReason.reason); +} + +export async function ShutdownIdleRamsgate(Server: Gameserver, Decision: GameserverIdleDecision){ + if(RamsgateServer !== Server || Server.expectedShutdownReason != undefined){ + return; + } + const Operation = RamsgateShutdown ??= ShutdownIdleGameserver(Server, Decision); + try{ + await Operation; + } + finally{ + if(RamsgateShutdown === Operation){ + RamsgateShutdown = undefined; + } + } +} + export function GetTrainingDojoConnectionDetails(){ + if(TrainingDojoServer == undefined){ + throw new Error("Training Dojo server has not started"); + } + + TouchGameserver(TrainingDojoServer); + return { + id: TrainingDojoServer.id, host: MY_IP, port: TrainingDojoServer.port }; } -export async function StartupGameserverWithArgs(GameArgs: string){ +function FindGameserverForPlayer(PlayerId: string){ + const Assigned = PlayerServerAssignments.get(PlayerId); + if(Assigned == undefined) + return undefined; + if(Gameservers.includes(Assigned) && Assigned.expectedShutdownReason == undefined) + return Assigned; + RemovePlayerAssignment(PlayerId); + return undefined; +} + +export function CleanupPlayerAssignment(PlayerId: string){ + RemovePlayerAssignment(PlayerId); +} + +export function CleanupStaleAssignments(Budget = 64){ + let Checked = 0; + for(const [PlayerId, Server] of PlayerServerAssignments){ + if(Checked++ >= Budget) + break; + if(!Gameservers.includes(Server) || Server.expectedShutdownReason != undefined) + RemovePlayerAssignment(PlayerId); + } +} + +// mm timing logging, no gameplay effect +export function GetRamsgatePrewarmStatus(){ + return { + ready: RamsgateServer != undefined, + restarting: RamsgateStartup != undefined, + port: RamsgateServer?.port + }; +} + +function FindHuntGameserversForPlayers(PlayerIds: string[]){ + const Players = new Set(PlayerIds); + return Gameservers.filter((Candidate) => + !Candidate.isRamsgate + && !Candidate.isTrainingDojo + && Candidate.expectedShutdownReason == undefined + && Candidate.expectedPlayers?.some((Player) => Players.has(Player.playerUid))); +} + +function DetachPlayersFromGameservers(Servers: Gameserver[], PlayerIds: string[]){ + const Players = new Set(PlayerIds); + + for(const Server of Servers){ + Server.expectedPlayers = Server.expectedPlayers?.filter((Player) => !Players.has(Player.playerUid)); + } +} + +function RetireReplacedGameservers(Servers: Gameserver[], PlayerIds: string[], Replacement: Gameserver){ + if(Servers.length === 0){ + return; + } + + DetachPlayersFromGameservers(Servers, PlayerIds); + logger.info({ + replacementServerId: Replacement.id, + replacementPort: Replacement.port, + playerIds: PlayerIds, + retiredServers: Servers.map((Server) => ({id: Server.id, port: Server.port, processId: Server.processId})) + }, "Retiring gameservers replaced by a fresh instance"); + + for(const Server of Servers){ + void ShutdownServer(Server, `replaced_by_fresh_instance:${Replacement.id}`).catch((Error) => { + logger.error(Error, `Failed to retire replaced gameserver ${Server.id} on port ${Server.port}`); + }); + } +} + +export async function GetGameserverStatusForPlayer(PlayerId: string){ + const Server = FindGameserverForPlayer(PlayerId); + + if(Server == undefined){ + return { + found: false, + joinable: false + }; + } + + const ProcessAlive = IsGameserverProcessAlive(Server); + const PortBound = await IsUdpPortInUse(Server.port); + const Joinable = ProcessAlive && PortBound && Server.expectedShutdownReason == undefined; + + return { + found: true, + joinable: Joinable, + server: { + host: MY_IP, + port: Server.port, + id: Server.id, + origin: Server.origin, + map: Server.map, + matchmakerHuntId: Server.matchmakerHuntId, + expectedPlayers: Server.expectedPlayers, + processAlive: ProcessAlive, + portBound: PortBound, + lastTouchedTime: Server.lastTouchedTime.toISOString() + } + }; +} + +export function TouchGameserverForPlayer(PlayerId: string){ + const Server = FindGameserverForPlayer(PlayerId); + + if(Server == undefined){ + return undefined; + } + + TouchGameserver(Server); + + return { + host: MY_IP, + port: Server.port, + id: Server.id, + origin: Server.origin, + lastTouchedTime: Server.lastTouchedTime.toISOString() + }; +} + +export async function GetOrStartTrainingDojoConnectionDetails( + Origin: GameserverOrigin, + ExpectedPlayers: string[] = [] +){ + if(TrainingDojoServer != undefined){ + AssignPlayersToServer(TrainingDojoServer, + TransformExpectedPlayers(undefined, ExpectedPlayers) ?? []); + return GetTrainingDojoConnectionDetails(); + } + + if(TrainingDojoStartup == undefined){ + TrainingDojoStartup = StartServer({ + map: TRAINING_DOJO_MAP_PATH, + behemoth: undefined, + matchmakerHuntId: undefined, + expectedPlayers: undefined, + isRamsgate: false, + isTrainingDojo: true, + origin: Origin, + trigger: "training_dojo_matchmaking", + shutdownAfterSeconds: TRAINING_DOJO_IDLE_SHUTDOWN_SECONDS + }).then((Server) => { + TrainingDojoServer = Server; + return Server; + }).finally(() => { + TrainingDojoStartup = undefined; + }); + } + + const Server = await TrainingDojoStartup; + TouchGameserver(Server); + AssignPlayersToServer(Server, + TransformExpectedPlayers(undefined, ExpectedPlayers) ?? []); + + return { + id: Server.id, + host: MY_IP, + port: Server.port + }; +} + +function TransformExpectedPlayers(HuntId: string | undefined, ExpectedPlayers: string[] | undefined){ + return ExpectedPlayers?.map((PlayerId) => ({ + playerUid: PlayerId, + playerHuntId: HuntId ?? "UNKNOWN_HUNT" + })); +} + +export async function StartupGameserverWithArgs(GameArgs: string, ExpectedPlayers: string[] | undefined){ const Map = GameArgs.split("?")[0]; const Behemoth = GameArgs.split("?")[2].split("=")[1]; + const ShutdownAfterSeconds = GetHuntIdleShutdownSeconds(undefined, undefined, GameArgs); - const GameServerToReturn = await StartServer(Map, Behemoth, undefined, undefined, false, false); + const GameServerToReturn = await StartServer({ + map: Map, + behemoth: Behemoth, + matchmakerHuntId: undefined, + expectedPlayers: TransformExpectedPlayers(undefined, ExpectedPlayers), + isRamsgate: false, + isTrainingDojo: false, + origin: "HUNT_ARGS", + trigger: GameArgs, + shutdownAfterSeconds: ShutdownAfterSeconds + }); + AssignPlayersToServer(GameServerToReturn, + TransformExpectedPlayers(undefined, ExpectedPlayers) ?? []); return { + id: GameServerToReturn.id, host: MY_IP, port: GameServerToReturn.port }; } function GetMatchmakerHuntIdFromPlayerHuntId(PlayerHuntId: string){ - const MatchmakerHuntIDs = (PlayerHuntTable[0].Rows as any)[PlayerHuntId].MatchmakerHuntIDs; + const MatchmakerHuntIDs = PLAYER_HUNT_ROWS[PlayerHuntId].MatchmakerHuntIDs; let MatchmakerHuntObject; @@ -177,13 +1646,13 @@ function GetMatchmakerHuntIdFromPlayerHuntId(PlayerHuntId: string){ } function GetBehemothPathFromMatchmakerHuntId(MatchmakerHuntId: string): string{ - const MatchmakerHuntObject = (MatchmakerHuntTable[0].Rows as any)[MatchmakerHuntId]; + const MatchmakerHuntObject = MATCHMAKER_HUNT_ROWS[MatchmakerHuntId]; return MatchmakerHuntObject.SpecificBehemoth.BehemothAsset.AssetPathName; } function GetMapPathFromMatchmakerHuntId(MatchmakerHuntId: string): string{ - const MatchmakerHuntObject = (MatchmakerHuntTable[0].Rows as any)[MatchmakerHuntId]; + const MatchmakerHuntObject = MATCHMAKER_HUNT_ROWS[MatchmakerHuntId]; const MapList = MatchmakerHuntObject.MapList; @@ -191,38 +1660,165 @@ function GetMapPathFromMatchmakerHuntId(MatchmakerHuntId: string): string{ } function GetGameModeOverrideFromMatchmakerHuntId(MatchmakerHuntId: string): string{ - const MatchmakerHuntObject = (MatchmakerHuntTable[0].Rows as any)[MatchmakerHuntId]; + const MatchmakerHuntObject = MATCHMAKER_HUNT_ROWS[MatchmakerHuntId]; return MatchmakerHuntObject.GameModeOverride.replaceAll("Archon/Content", "/Game"); } -type TrialsData = { +type TrialData = { Behemoth: string; TrialsHuntId: string; } -function RandomlyGenTrialsData(IsElite: boolean): TrialsData{ +const TRIAL_HUNT_ID_REGEX = /^Arena_MatchmakerHunt_(Hard|Elite)_(\d{3})$/; +const TRIAL_PLAYER_HUNT_ID_REGEX = /^(Trials_PlayerHunt_(Hard|Elite)_(\d{3}))(?:_(?:Day|Night))?$/; +const TRIALS_HARD_ROWS = TrialsHardHuntTable[0].Rows as any; +const TRIALS_ELITE_ROWS = TrialsEliteHuntTable[0].Rows as any; +const PLAYER_HUNT_ROWS = PlayerHuntTable[0].Rows as any; +const MATCHMAKER_HUNT_ROWS = MatchmakerHuntTable[0].Rows as any; +const TRIAL_ROWS = { + Hard: TRIALS_HARD_ROWS, + Elite: TRIALS_ELITE_ROWS +} as const; +let TrialByHuntId: Map | undefined; +let DisplayHuntByBehemoth: Map | undefined; + +function MakeTrial(TrialsHuntId: string, Row: any): TrialData{ + return { + Behemoth: Row.SpecificBehemoth.BehemothAsset.AssetPathName, + TrialsHuntId: TrialsHuntId + }; +} + +function GetTrialIndex(){ + if(TrialByHuntId != undefined){ + return TrialByHuntId; + } + + TrialByHuntId = new Map(); + + for(const [Difficulty, Rows] of Object.entries(TRIAL_ROWS)){ + for(const [TrialsHuntId, Row] of Object.entries(Rows)){ + const Match = TRIAL_HUNT_ID_REGEX.exec(TrialsHuntId); + + if(Match == undefined || Match[1] !== Difficulty){ + continue; + } + + const Data = MakeTrial(TrialsHuntId, Row); + TrialByHuntId.set(TrialsHuntId, Data); + TrialByHuntId.set(`Trials_PlayerHunt_${Difficulty}_${Match[2]}`, Data); + } + } + + TrialByHuntId.set("CR19_PlayerHunt_Arena_Hard", TrialByHuntId.get("Arena_MatchmakerHunt_Hard_001")!); + TrialByHuntId.set("CR19_PlayerHunt_Arena_Elite", TrialByHuntId.get("Arena_MatchmakerHunt_Elite_001")!); + + logger.info({ + huntIdCount: TrialByHuntId.size + }, "Indexed trials hunt ids"); + + return TrialByHuntId; +} + +function ResolveTrial(PlayerHuntId: string): TrialData | undefined{ + const AliasMatch = TRIAL_PLAYER_HUNT_ID_REGEX.exec(PlayerHuntId); + const BasePlayerHuntId = AliasMatch?.[1] ?? PlayerHuntId; + const Trial = GetTrialIndex().get(BasePlayerHuntId); + + if(Trial != undefined) + return Trial; + + return PlayerHuntId.includes("Arena") ? RandomlyGenTrialsData(PlayerHuntId.includes("Elite")) : undefined; +} + +function GetMatchmakerBehemothAssetPath(MatchmakerHuntId: string): string | undefined{ + const MatchmakerHuntObject = MATCHMAKER_HUNT_ROWS[MatchmakerHuntId]; + const BehemothAssetPath = MatchmakerHuntObject?.SpecificBehemoth?.BehemothAsset?.AssetPathName; + + return typeof BehemothAssetPath === "string" ? BehemothAssetPath : undefined; +} + +function GetPlayerHuntMatchmakerIds(PlayerHunt: any): string[]{ + const MatchmakerHuntIDs = PlayerHunt?.MatchmakerHuntIDs; + + if(!Array.isArray(MatchmakerHuntIDs)){ + return []; + } + + return MatchmakerHuntIDs + .map((MatchmakerHuntID: any) => MatchmakerHuntID?.RowName) + .filter((MatchmakerHuntId: any): MatchmakerHuntId is string => typeof MatchmakerHuntId === "string"); +} + +function GetDisplayHuntIndex(){ + if(DisplayHuntByBehemoth != undefined){ + return DisplayHuntByBehemoth; + } + + DisplayHuntByBehemoth = new Map(); + + for(const [PlayerHuntId, PlayerHunt] of Object.entries(PLAYER_HUNT_ROWS)){ + for(const MatchmakerHuntId of GetPlayerHuntMatchmakerIds(PlayerHunt)){ + const BehemothAssetPath = GetMatchmakerBehemothAssetPath(MatchmakerHuntId); + + if(BehemothAssetPath == undefined || BehemothAssetPath.trim().length === 0){ + continue; + } + + const ExistingPlayerHuntId = DisplayHuntByBehemoth.get(BehemothAssetPath); + const IsPreferredPursuit = PlayerHuntId.startsWith("CR19_PlayerHunt_Pursuit_"); + const HasPreferredPursuit = ExistingPlayerHuntId?.startsWith("CR19_PlayerHunt_Pursuit_") === true; + + if(ExistingPlayerHuntId == undefined || (IsPreferredPursuit && !HasPreferredPursuit)){ + DisplayHuntByBehemoth.set(BehemothAssetPath, PlayerHuntId); + } + } + } + + logger.info({ + behemothCount: DisplayHuntByBehemoth.size + }, "Indexed trial display player hunts"); + + return DisplayHuntByBehemoth; +} + +function GetDisplayHuntId(Trial: TrialData | undefined): string | undefined{ + if(Trial == undefined || Trial.Behemoth.trim().length === 0){ + return undefined; + } + + return GetDisplayHuntIndex().get(Trial.Behemoth); +} + +function GetExpectedHuntId(Trial: TrialData | undefined, FallbackPlayerHuntId: string){ + const DisplayPlayerHuntId = GetDisplayHuntId(Trial); + + if(Trial != undefined && DisplayPlayerHuntId == undefined){ + logger.warn(`No canonical display player hunt found for trial ${Trial.TrialsHuntId}; falling back to ${FallbackPlayerHuntId}`); + } + + return DisplayPlayerHuntId ?? FallbackPlayerHuntId; +} + +function RandomlyGenTrialsData(IsElite: boolean): TrialData{ const RandomTrialNum = String(crypto.randomInt(1, 89)).padStart(3, "0"); const Difficulty = IsElite ? "Elite" : "Hard"; const TrialsHuntId = `Arena_MatchmakerHunt_${Difficulty}_${RandomTrialNum}`; - const Row = IsElite ? (TrialsEliteHuntTable[0].Rows as any)[TrialsHuntId] : (TrialsHardHuntTable[0].Rows as any)[TrialsHuntId]; + const Row = IsElite ? TRIALS_ELITE_ROWS[TrialsHuntId] : TRIALS_HARD_ROWS[TrialsHuntId]; - const Behemoth = Row.SpecificBehemoth.BehemothAsset.AssetPathName; - - return { - Behemoth: Behemoth, - TrialsHuntId: TrialsHuntId - }; + return MakeTrial(TrialsHuntId, Row); } -export async function StartupGameserverWithHuntIdAndPlayers(HuntId: string, ExpectedPlayers: string[]){ - const TrialsData = HuntId.includes("Arena") ? RandomlyGenTrialsData(HuntId.includes("Elite")) : undefined; - const MatchmakerHuntId = TrialsData == undefined ? GetMatchmakerHuntIdFromPlayerHuntId(HuntId) : TrialsData.TrialsHuntId; - let BehemothPath = TrialsData == undefined ? GetBehemothPathFromMatchmakerHuntId(MatchmakerHuntId!) : TrialsData.Behemoth; - let MapPath = TrialsData == undefined ? GetMapPathFromMatchmakerHuntId(MatchmakerHuntId!) : TRIALS_MAP_PATH; +export async function StartupGameserverWithHuntIdAndPlayers(HuntId: string, ExpectedPlayers: string[], FreshInstance = false){ + const Trial = ResolveTrial(HuntId); + const MatchmakerHuntId = Trial == undefined ? GetMatchmakerHuntIdFromPlayerHuntId(HuntId) : Trial.TrialsHuntId; + let BehemothPath = Trial == undefined ? GetBehemothPathFromMatchmakerHuntId(MatchmakerHuntId!) : Trial.Behemoth; + let MapPath = Trial == undefined ? GetMapPathFromMatchmakerHuntId(MatchmakerHuntId!) : TRIALS_MAP_PATH; + const ExpectedPlayerHuntId = GetExpectedHuntId(Trial, HuntId); if(MatchmakerHuntId != undefined && !MatchmakerHuntId.includes("Arena")){ const OverrideGameMode = GetGameModeOverrideFromMatchmakerHuntId(MatchmakerHuntId); @@ -233,14 +1829,82 @@ export async function StartupGameserverWithHuntIdAndPlayers(HuntId: string, Expe } } - const GameServerToReturn = await StartServer(MapPath, BehemothPath, MatchmakerHuntId, ExpectedPlayers.map((PlayerId) => { - return { - playerUid: PlayerId, - playerHuntId: HuntId - }; - }), false, false); + logger.info({ + huntId: HuntId, + matchmakerHuntId: MatchmakerHuntId, + expectedPlayerHuntId: ExpectedPlayerHuntId, + behemoth: BehemothPath, + map: MapPath, + expectedPlayers: ExpectedPlayers, + freshInstance: FreshInstance + }, "Resolved hunt gameserver launch"); + + const StartOptions: StartServerOptions = { + map: MapPath, + behemoth: BehemothPath, + matchmakerHuntId: MatchmakerHuntId, + expectedPlayers: TransformExpectedPlayers(ExpectedPlayerHuntId, ExpectedPlayers), + isRamsgate: false, + isTrainingDojo: false, + origin: "HUNT_MATCHMAKER", + trigger: HuntId, + shutdownAfterSeconds: GetHuntIdleShutdownSeconds(HuntId, MatchmakerHuntId, MapPath) + }; + + const PreviousServers = FreshInstance ? FindHuntGameserversForPlayers(ExpectedPlayers) : []; + let RetiredBeforeStartup = false; + + const RetireBeforeStartup = async () => { + if(RetiredBeforeStartup || PreviousServers.length === 0){ + return; + } + + RetiredBeforeStartup = true; + logger.info({ + huntId: HuntId, + playerIds: ExpectedPlayers, + servers: PreviousServers.map((Server) => ({id: Server.id, port: Server.port, processId: Server.processId})) + }, "No free hunt port; retiring previous gameservers before fresh startup"); + await Promise.all(PreviousServers.map((Server) => ShutdownServer(Server, "fresh_instance_port_required"))); + }; + + if(FreshInstance && PreviousServers.length > 0 && FreePorts.length === 0){ + await RetireBeforeStartup(); + } + + let GameServerToReturn: Gameserver; + try{ + GameServerToReturn = await StartServer(StartOptions); + } + catch(Error){ + if(FreshInstance && PreviousServers.length > 0 && !RetiredBeforeStartup && Error instanceof NoFreeHuntPortsError){ + await RetireBeforeStartup(); + GameServerToReturn = await StartServer(StartOptions); + } + else{ + throw Error; + } + } + + if(FreshInstance && !RetiredBeforeStartup){ + RetireReplacedGameservers(PreviousServers, ExpectedPlayers, GameServerToReturn); + } + + if(FreshInstance){ + logger.info({ + huntId: HuntId, + playerIds: ExpectedPlayers, + serverId: GameServerToReturn.id, + port: GameServerToReturn.port, + replacementMode: RetiredBeforeStartup ? "old_first_no_free_port" : "new_first" + }, "Fresh gameserver instance ready"); + } + + AssignPlayersToServer(GameServerToReturn, + TransformExpectedPlayers(ExpectedPlayerHuntId, ExpectedPlayers) ?? []); return { + id: GameServerToReturn.id, host: MY_IP, port: GameServerToReturn.port } @@ -251,7 +1915,9 @@ export async function Startup(){ FreePorts.push(i); } - RamsgateServer = await StartServer(RAMSGATE_MAP_PATH, undefined, undefined, undefined, true, false); + logger.info("Ramsgate will start on the first CITY request"); - TrainingDojoServer = await StartServer(TRAINING_DOJO_MAP_PATH, undefined, undefined, undefined, false, true); -} \ No newline at end of file + if(PREWARM_TRAINING_DOJO){ + await GetOrStartTrainingDojoConnectionDetails("TRAINING_DOJO_PREWARM"); + } +} diff --git a/UndauntedDeployServer/src/controllers/matchmaker.ts b/UndauntedDeployServer/src/controllers/matchmaker.ts index 3d9244c..86c361b 100644 --- a/UndauntedDeployServer/src/controllers/matchmaker.ts +++ b/UndauntedDeployServer/src/controllers/matchmaker.ts @@ -1,30 +1,38 @@ import { logger } from "../logger"; -import { GetRamsgateConnectionDetails, GetTrainingDojoConnectionDetails, StartupGameserverWithArgs, StartupGameserverWithHuntIdAndPlayers } from "./gameservers"; +import { GetOrStartTrainingDojoConnectionDetails, GetRamsgateConnectionDetails, StartupGameserverWithArgs, StartupGameserverWithHuntIdAndPlayers } from "./gameservers"; -export async function HandleMatchmakingRequest(GameMode: string, GameArgs: string, HuntId: string, ExpectedPlayers: string[] | undefined){ - logger.info(`Handling matchmaking with GameMode: ${GameMode} HuntId: ${HuntId} and GameArgs: ${GameArgs} and ExpectedPlayers ${ExpectedPlayers}`); +export async function HandleMatchmakingRequest(GameMode: string, GameArgs: string, HuntId: string, ExpectedPlayers: string[] | undefined, FreshInstance = false){ + const NormalizedHuntId = HuntId ?? ""; + const NormalizedExpectedPlayers = ExpectedPlayers ?? []; + + logger.info({ + gameMode: GameMode, + huntId: NormalizedHuntId, + gameArgs: GameArgs, + expectedPlayers: NormalizedExpectedPlayers, + freshInstance: FreshInstance + }, "Handling matchmaking"); if(GameMode === "CITY"){ - return GetRamsgateConnectionDetails(); + return await GetRamsgateConnectionDetails(NormalizedExpectedPlayers); } else if(GameMode === "SHARED"){ - if (HuntId != undefined && HuntId.trim().length > 0){ - if(HuntId == "ShatteredIsles_TrainingDojo"){ - return GetTrainingDojoConnectionDetails(); - } + if(NormalizedHuntId === "ShatteredIsles_TrainingDojo"){ + return await GetOrStartTrainingDojoConnectionDetails( + "TRAINING_DOJO_LAZY", NormalizedExpectedPlayers); } } else if(GameMode === "ISLAND"){ if(GameArgs != undefined && GameArgs.trim().length > 0){ - return await StartupGameserverWithArgs(GameArgs); + return await StartupGameserverWithArgs(GameArgs, ExpectedPlayers); } - if(HuntId != undefined && HuntId.trim().length > 0 && ExpectedPlayers != undefined){ - return await StartupGameserverWithHuntIdAndPlayers(HuntId, ExpectedPlayers!); + if(NormalizedHuntId.length > 0 && ExpectedPlayers != undefined){ + return await StartupGameserverWithHuntIdAndPlayers(NormalizedHuntId, ExpectedPlayers, FreshInstance); } } logger.error("Matchmaking failed, sending you to Ramsgate!"); - return GetRamsgateConnectionDetails(); -} \ No newline at end of file + return await GetRamsgateConnectionDetails(NormalizedExpectedPlayers); +} diff --git a/UndauntedDeployServer/src/controllers/watchdog.ts b/UndauntedDeployServer/src/controllers/watchdog.ts index bded83a..92f54e3 100644 --- a/UndauntedDeployServer/src/controllers/watchdog.ts +++ b/UndauntedDeployServer/src/controllers/watchdog.ts @@ -1,6 +1,6 @@ import { kill } from "node:process"; import { logger } from "../logger"; -import { Gameserver, Gameservers, CleanupServer } from "./gameservers"; +import { Gameserver, Gameservers, CleanupServer, CleanupStaleAssignments, GetGameserverIdleShutdownReason, ShutdownIdleGameserver, ShutdownIdleRamsgate } from "./gameservers"; /** * TODO: @@ -18,13 +18,29 @@ function IsGameserverStillAlive(GameserverToCheck: Gameserver){ } export async function RunWatchdog(){ - logger.info(`Running Gameserver Watchdog!`); + // logger.info(`Running Gameserver Watchdog!`); - for(const Gameserver of Gameservers){ + CleanupStaleAssignments(); + for(const Gameserver of [...Gameservers]){ if(!IsGameserverStillAlive(Gameserver)){ - console.log(`Cleaning up Gameserver on port ${Gameserver.port}`); + logger.info(`Cleaning up dead gameserver on port ${Gameserver.port}`); - CleanupServer(Gameserver); + await CleanupServer(Gameserver); + + continue; + } + + if(Gameserver.isRamsgate){ + const IdleDecision = GetGameserverIdleShutdownReason(Gameserver); + if(IdleDecision != undefined){ + await ShutdownIdleRamsgate(Gameserver, IdleDecision); + } + continue; + } + + const IdleDecision = GetGameserverIdleShutdownReason(Gameserver); + if(IdleDecision != undefined){ + await ShutdownIdleGameserver(Gameserver, IdleDecision); } } -} \ No newline at end of file +} diff --git a/UndauntedDeployServer/src/routes/gameservers.ts b/UndauntedDeployServer/src/routes/gameservers.ts new file mode 100644 index 0000000..3445d47 --- /dev/null +++ b/UndauntedDeployServer/src/routes/gameservers.ts @@ -0,0 +1,19 @@ +import { Router } from "express"; +import { HandleGameserverHeartbeat, HandleGameserverReadyCallback } from "../controllers/gameservers"; + +export const gameserverRouter = Router(); + +gameserverRouter.post("/ready", (req, res) => { + const Result = HandleGameserverReadyCallback(req.header("x-undaunted-ready-token"), req.body); + + res.status(Result.status); + res.json(Result.body); +}); + +gameserverRouter.post("/heartbeat", (req, res) => { + const Result = HandleGameserverHeartbeat( + req.header("x-undaunted-lifecycle-token"), req.body); + + res.status(Result.status); + res.json(Result.body); +}); diff --git a/UndauntedDeployServer/src/routes/matchmaker.ts b/UndauntedDeployServer/src/routes/matchmaker.ts index 1927cc6..1ef5439 100644 --- a/UndauntedDeployServer/src/routes/matchmaker.ts +++ b/UndauntedDeployServer/src/routes/matchmaker.ts @@ -1,18 +1,81 @@ import { Router } from "express"; import { logger } from "../logger"; import { HandleMatchmakingRequest } from "../controllers/matchmaker"; +import { GetGameserverStatusForPlayer, GetRamsgatePrewarmStatus, TouchGameserverForPlayer } from "../controllers/gameservers"; import express from "express"; export const matchmakingRouter = Router(); matchmakingRouter.post("/handle-matchmaking-for-player", express.json(), async (req, res) => { + const StartedAt = performance.now(); const GameMode = req.body.GameMode; const GameArgs = req.body.GameArgs; const HuntId = req.body.HuntId; const ExpectedPlayers = req.body.ExpectedPlayers; + const FreshInstance = req.body.FreshInstance === true; - const MatchmakingResult = await HandleMatchmakingRequest(GameMode, GameArgs, HuntId, ExpectedPlayers); + try{ + const MatchmakingResult = await HandleMatchmakingRequest(GameMode, GameArgs, HuntId, ExpectedPlayers, FreshInstance); + const DurationMs = Math.round(performance.now() - StartedAt); + + logger.info({ + gameMode: GameMode, + huntId: HuntId ?? "", + durationMs: DurationMs, + ramsgatePrewarm: GameMode === "CITY" ? GetRamsgatePrewarmStatus() : undefined + }, "Matchmaking response ready"); + + res.status(200); + res.json(MatchmakingResult); + } + catch(Error){ + const DurationMs = Math.round(performance.now() - StartedAt); + logger.error(Error, `Failed to handle matchmaking for GameMode ${GameMode} HuntId ${HuntId}`); + logger.info({ gameMode: GameMode, huntId: HuntId ?? "", durationMs: DurationMs }, "Matchmaking response failed"); + + res.status(503); + res.json({ + error: "gameserver_startup_failed" + }); + } +}); + +matchmakingRouter.post("/touch-player", express.json(), (req, res) => { + const PlayerId = req.body.PlayerId; + + if(typeof PlayerId !== "string" || PlayerId.length === 0){ + res.status(400); + res.json({touched: false}); + return; + } + + const TouchedServer = TouchGameserverForPlayer(PlayerId); + + if(TouchedServer == undefined){ + res.status(404); + res.json({touched: false}); + return; + } + + res.status(200); + res.json({ + touched: true, + server: TouchedServer + }); +}); + +matchmakingRouter.post("/player-server-status", express.json(), async (req, res) => { + const PlayerId = req.body.PlayerId; + + if(typeof PlayerId !== "string" || PlayerId.length === 0){ + res.status(400); + res.json({ + found: false, + joinable: false + }); + return; + } res.status(200); - res.json(MatchmakingResult); -}); \ No newline at end of file + res.json(await GetGameserverStatusForPlayer(PlayerId)); +}); diff --git a/UndauntedDeployServer/src/server.ts b/UndauntedDeployServer/src/server.ts index 00357c8..22db860 100644 --- a/UndauntedDeployServer/src/server.ts +++ b/UndauntedDeployServer/src/server.ts @@ -5,11 +5,17 @@ import { logger } from "./logger"; const PORT = process.env.PORT; -app.listen(PORT, () => { - Startup(); +app.listen(PORT, async () => { + try{ + await Startup(); - setInterval(RunWatchdog, 60 * 1000); + setInterval(RunWatchdog, 5 * 1000); - logger.info(`Undaunted DeployServer on port ${PORT}`); - logger.info(`Clear Skies, Slayer.`); -}); \ No newline at end of file + logger.info(`Undaunted DeployServer on port ${PORT}`); + logger.info(`Clear Skies, Slayer.`); + } + catch(Error){ + logger.fatal(Error, "Undaunted DeployServer startup failed"); + process.exitCode = 1; + } +}); diff --git a/UndauntedDeployServer/tsconfig.json b/UndauntedDeployServer/tsconfig.json index 814fd9e..6fd64a1 100644 --- a/UndauntedDeployServer/tsconfig.json +++ b/UndauntedDeployServer/tsconfig.json @@ -9,7 +9,9 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo" }, "include": ["src/**/*.ts"] -} \ No newline at end of file +} diff --git a/UndauntedInternalServer/Hooks/ClientCombatCompatibility.inl b/UndauntedInternalServer/Hooks/ClientCombatCompatibility.inl new file mode 100644 index 0000000..9cb568b --- /dev/null +++ b/UndauntedInternalServer/Hooks/ClientCombatCompatibility.inl @@ -0,0 +1,214 @@ +namespace ClientCombatCompatibility { + constexpr uint64_t ReviveIntentTimeoutMilliseconds = 1000; + + struct PendingReviveIntent { + StableObjectIdentity Pawn{}; + StableObjectIdentity World{}; + ELoadoutQuickItemSlot Slot = ELoadoutQuickItemSlot::Slot_Flask_Revive; + uint64_t QueuedAtMilliseconds = 0; + }; + + static PendingReviveIntent PendingRevive{}; + static StableObjectIdentity BleedoutAcknowledgedPawn{}; + static UFunction* TryUseQuickItemFunction = nullptr; + static UFunction* ReviveFromBleedoutFunction = nullptr; + static UFunction* ReviveFromBleedoutBlueprintFunction = nullptr; + static UFunction* SetBleedoutStateFunction = nullptr; + static UFunction* ClientBleedoutStartedFunction = nullptr; + static UFunction* TickStaminaFunction = nullptr; + static UFunction* EvaluateStaminaFunction = nullptr; + static uint64_t LastFunctionResolveAttemptMilliseconds = 0; + static bool ServerOnlyStaminaSuppressionEnabled = false; + + static bool AllFunctionsResolved() { + return TryUseQuickItemFunction && ReviveFromBleedoutFunction && + ReviveFromBleedoutBlueprintFunction && SetBleedoutStateFunction && + ClientBleedoutStartedFunction && TickStaminaFunction && EvaluateStaminaFunction; + } + + static void ResolveFunctions() { + if (AllFunctionsResolved()) + return; + const uint64_t NowMilliseconds = GetTickCount64(); + if (NowMilliseconds - LastFunctionResolveAttemptMilliseconds < 1000) + return; + LastFunctionResolveAttemptMilliseconds = NowMilliseconds; + + UClass* PlayerClass = ABP_PlayerCharacter_C::StaticClass(); + if (PlayerClass) { + if (!TryUseQuickItemFunction) + TryUseQuickItemFunction = PlayerClass->GetFunction( + "BP_PlayerCharacter_C", "TryUseQuickItem"); + if (!ReviveFromBleedoutBlueprintFunction) + ReviveFromBleedoutBlueprintFunction = PlayerClass->GetFunction( + "BP_PlayerCharacter_C", "BP_ReviveFromBleedout"); + if (!SetBleedoutStateFunction) + SetBleedoutStateFunction = PlayerClass->GetFunction( + "BP_PlayerCharacter_C", "Set Bleedout State"); + if (!TickStaminaFunction) + TickStaminaFunction = PlayerClass->GetFunction( + "BP_PlayerCharacter_C", "TickStamina"); + if (!EvaluateStaminaFunction) + EvaluateStaminaFunction = PlayerClass->GetFunction( + "BP_PlayerCharacter_C", "Evaluate Stamina System"); + } + UClass* CharacterClass = AArchonCharacter::StaticClass(); + if (!ReviveFromBleedoutFunction && CharacterClass) + ReviveFromBleedoutFunction = CharacterClass->GetFunction( + "ArchonCharacter", "BP_ReviveFromBleedout"); + UClass* BleedoutClass = UArchonBleedoutComponent::StaticClass(); + if (!ClientBleedoutStartedFunction && BleedoutClass) + ClientBleedoutStartedFunction = BleedoutClass->GetFunction( + "ArchonBleedoutComponent", "ClientOnBleedoutStarted"); + } + + static void ClearPendingRevive(const char* Diagnostic = nullptr) { + if (PendingRevive.Pawn.Object && Diagnostic) + OutputDebugStringA(Diagnostic); + PendingRevive = {}; + } + + static bool IsLocalPawn(APawn* Pawn) { + if (!Pawn) + return false; + const StableObjectIdentity PawnIdentity = TrackStableObject(Pawn); + if (ResolveStableObject(PawnIdentity) != Pawn) + return false; + AController* Controller = Pawn->Controller; + const StableObjectIdentity ControllerIdentity = TrackStableObject(Controller); + if (ResolveStableObject(ControllerIdentity) != Controller || + !Controller->IsA(APlayerController::StaticClass())) + return false; + APlayerController* PlayerController = static_cast(Controller); + UPlayer* Player = PlayerController->Player; + const StableObjectIdentity PlayerIdentity = TrackStableObject(Player); + return ResolveStableObject(PlayerIdentity) == Player && + Player->IsA(ULocalPlayer::StaticClass()) && PlayerController->Pawn == Pawn; + } + + static void BeginReviveIntent(ABP_PlayerCharacter_C* Pawn, + ELoadoutQuickItemSlot Slot, UWorld* World) { + if (PendingRevive.Pawn.Object) + return; + PendingRevive.Pawn = TrackStableObject(Pawn); + PendingRevive.World = TrackStableObject(World); + PendingRevive.Slot = Slot; + PendingRevive.QueuedAtMilliseconds = GetTickCount64(); + OutputDebugStringA("[Undaunted] buffered early self-revive until bleedout acknowledgement\n"); + } + + static void AcknowledgeBleedout(APawn* Pawn) { + if (!Pawn || !IsLocalPawn(Pawn)) + return; + BleedoutAcknowledgedPawn = TrackStableObject(Pawn); + if (!PendingRevive.Pawn.Object || ResolveStableObject(PendingRevive.Pawn) != Pawn || + !TryUseQuickItemFunction || !OrigProcessEvent) + return; + if (GetTickCount64() - PendingRevive.QueuedAtMilliseconds > + ReviveIntentTimeoutMilliseconds) { + ClearPendingRevive("[Undaunted] cancelled expired buffered self-revive\n"); + return; + } + + Params::BP_PlayerCharacter_C_TryUseQuickItem ReplayParams{}; + ReplayParams.QuickItemIndex = PendingRevive.Slot; + ClearPendingRevive(); + reinterpret_cast(OrigProcessEvent)( + Pawn, TryUseQuickItemFunction, &ReplayParams); + OutputDebugStringA("[Undaunted] replayed self-revive after bleedout acknowledgement\n"); + } + + static void ClearBleedout(APawn* Pawn) { + if (!Pawn || ResolveStableObject(BleedoutAcknowledgedPawn) == Pawn) + BleedoutAcknowledgedPawn = {}; + if (!Pawn || ResolveStableObject(PendingRevive.Pawn) == Pawn) + ClearPendingRevive(); + } + + static void HandleProcessEvent(UObject* Object, UFunction* Function, void* Parms) { + const auto CallOriginal = [&]() { + reinterpret_cast(OrigProcessEvent)( + Object, Function, Parms); + }; + + const bool IsPlayerCharacter = Object->IsA(ABP_PlayerCharacter_C::StaticClass()); + if (ServerOnlyStaminaSuppressionEnabled && IsPlayerCharacter && + (!TickStaminaFunction || !EvaluateStaminaFunction)) { + const std::string IncomingName = Function->GetName(); + if (!TickStaminaFunction && IncomingName == "TickStamina") + TickStaminaFunction = Function; + else if (!EvaluateStaminaFunction && IncomingName == "Evaluate Stamina System") + EvaluateStaminaFunction = Function; + } + if (ServerOnlyStaminaSuppressionEnabled && IsPlayerCharacter && + (Function == TickStaminaFunction || Function == EvaluateStaminaFunction)) { + return; + } + + if (Function == TryUseQuickItemFunction && IsPlayerCharacter && Parms) { + APawn* Pawn = static_cast(Object); + const auto* QuickItemParams = + static_cast(Parms); + if (QuickItemParams->QuickItemIndex == ELoadoutQuickItemSlot::Slot_Flask_Revive && + IsLocalPawn(Pawn) && ResolveStableObject(BleedoutAcknowledgedPawn) != Pawn) { + BeginReviveIntent(static_cast(Object), + QuickItemParams->QuickItemIndex, UWorld::GetWorld()); + return; + } + } + else if ((Function == ReviveFromBleedoutFunction || + Function == ReviveFromBleedoutBlueprintFunction) && + Object->IsA(AArchonCharacter::StaticClass())) { + const StableObjectIdentity PawnIdentity = TrackStableObject(Object); + CallOriginal(); + if (APawn* Pawn = static_cast(ResolveStableObject(PawnIdentity))) + ClearBleedout(Pawn); + return; + } + else if (Function == SetBleedoutStateFunction && IsPlayerCharacter && Parms) { + const StableObjectIdentity PawnIdentity = TrackStableObject(Object); + const bool BleedingOut = + static_cast( + Parms)->Bleeding_Out_; + CallOriginal(); + if (!BleedingOut) { + if (APawn* Pawn = static_cast(ResolveStableObject(PawnIdentity))) + ClearBleedout(Pawn); + } + return; + } + else if (Function == ClientBleedoutStartedFunction && + Object->IsA(UArchonBleedoutComponent::StaticClass())) { + const StableObjectIdentity OwnerIdentity = + TrackStableObject(FindStableOwningActor(Object)); + CallOriginal(); + AActor* Owner = static_cast(ResolveStableObject(OwnerIdentity)); + if (Owner && Owner->IsA(APawn::StaticClass())) + AcknowledgeBleedout(static_cast(Owner)); + return; + } + CallOriginal(); + } +} + +static void PrepareClientCombatCompatibility() { + if (ClientCombatCompatibility::ServerOnlyStaminaSuppressionEnabled) + ClientCombatCompatibility::ResolveFunctions(); +} + +static void TickClientCombatCompatibility(UWorld* World) { + using namespace ClientCombatCompatibility; + ResolveFunctions(); + + if (!PendingRevive.Pawn.Object) + return; + APawn* Pawn = static_cast(ResolveStableObject(PendingRevive.Pawn)); + if (!Pawn || ResolveStableObject(PendingRevive.World) != World || !IsLocalPawn(Pawn)) { + ClearPendingRevive("[Undaunted] cancelled self-revive after pawn/world transition\n"); + return; + } + if (GetTickCount64() - PendingRevive.QueuedAtMilliseconds > + ReviveIntentTimeoutMilliseconds) { + ClearPendingRevive("[Undaunted] cancelled self-revive acknowledgement timeout\n"); + } +} diff --git a/UndauntedInternalServer/Hooks/ProcessEventState.inl b/UndauntedInternalServer/Hooks/ProcessEventState.inl new file mode 100644 index 0000000..045813c --- /dev/null +++ b/UndauntedInternalServer/Hooks/ProcessEventState.inl @@ -0,0 +1,61 @@ +void* OrigProcessEvent = nullptr; + +struct StableObjectIdentity { + UObject* Object = nullptr; + int32_t Index = -1; + int32_t SerialNumber = 0; +}; + +static StableObjectIdentity TrackStableObject(UObject* Object) { + if (!Object || !UObject::GObjects || Object->Index < 0) + return {}; + FUObjectItem* Item = UObject::GObjects->GetItemByIndex(Object->Index); + if (!Item || Item->Object != Object) + return {}; + return { Object, Object->Index, Item->SerialNumber }; +} + +static UObject* ResolveStableObject(const StableObjectIdentity& Identity) { + if (!Identity.Object || !UObject::GObjects || Identity.Index < 0) + return nullptr; + FUObjectItem* Item = UObject::GObjects->GetItemByIndex(Identity.Index); + if (!Item || Item->Object != Identity.Object || Item->SerialNumber != Identity.SerialNumber || + (static_cast(Item->Flags) & 0x30000000u) != 0) + return nullptr; + UObject* Object = Item->Object; + if ((Object->Flags & EObjectFlags::BeginDestroyed) || + (Object->Flags & EObjectFlags::FinishDestroyed)) + return nullptr; + return Object; +} + +static AActor* FindStableOwningActor(UObject* Object) { + UObject* Current = Object; + for (uint8_t Depth = 0; Current && Depth < 8; ++Depth) { + const StableObjectIdentity Identity = TrackStableObject(Current); + if (ResolveStableObject(Identity) != Current) + return nullptr; + if (Current->IsA(AActor::StaticClass())) + return static_cast(Current); + Current = Current->Outer; + } + return nullptr; +} + +static bool HasPlayerCharacterOwner(AActor* Actor) { + AActor* Current = Actor; + for (uint8_t Depth = 0; Current && Depth < 8; ++Depth) { + const StableObjectIdentity Identity = TrackStableObject(Current); + if (ResolveStableObject(Identity) != Current) + return false; + if (Current->IsA(AArchonCharacter::StaticClass())) + return true; + AActor* Next = Current->Owner; + if (!Next && Current->Instigator) + Next = Current->Instigator; + if (Next == Current) + return false; + Current = Next; + } + return false; +} diff --git a/UndauntedInternalServer/MinHook/hook.c b/UndauntedInternalServer/MinHook/hook.c index 68a006a..600c874 100644 --- a/UndauntedInternalServer/MinHook/hook.c +++ b/UndauntedInternalServer/MinHook/hook.c @@ -30,7 +30,7 @@ #include #include -#include "../include/MinHook.h" +#include "MinHook.h" #include "buffer.h" #include "trampoline.h" diff --git a/UndauntedInternalServer/Networking.cpp b/UndauntedInternalServer/Networking.cpp deleted file mode 100644 index 0815c52..0000000 --- a/UndauntedInternalServer/Networking.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "networking.h" - -#include - -using namespace SDK; - -namespace Networking { - UNetDriver* NetDriver = nullptr; - static uintptr_t BaseAddress = 0x0; - - static std::vector BuildConsiderList(UWorld* World, UNetDriver* Driver) { - std::vector Actors = std::vector(); - - for (ULevel* Level : World->Levels) { - for (AActor* Actor : Level->Actors) { - if (!Actor) - continue; - - if (Actor->RemoteRole == ENetRole::ROLE_None) - continue; - - if (Actor->bActorIsBeingDestroyed) - continue; - - if (!reinterpret_cast(*(void**)((uintptr_t)Actor->VTable + 0x150))(Actor)) { - continue; - } - - reinterpret_cast(BaseAddress + 0x306B150)(Actor, Driver); - - Actors.push_back(Actor); - } - } - - /* - for (int i = 0; i < SDK::UObject::GObjects->Num(); i++) - { - SDK::UObject* Obj = SDK::UObject::GObjects->GetByIndex(i); - - if (!Obj) - continue; - - if (Obj->IsDefaultObject()) - continue; - - if (Obj->IsA(SDK::AActor::StaticClass())) - { - AActor* Actor = (AActor*)Obj; - - if (Actor->RemoteRole == ENetRole::ROLE_None) - continue; - - if (Actor->bActorIsBeingDestroyed) - continue; - - if (!reinterpret_cast(*(void**)((uintptr_t)Actor->VTable + 0x150))(Actor)) { - continue; - } - - reinterpret_cast(BaseAddress + 0x306B150)(Actor, Driver); - - Actors.push_back(Actor); - } - } - */ - - return Actors; - } - - static UActorChannel* GetActorChannelForConnectionAndActor(UNetConnection* Connection, AActor* Actor) { - for (UChannel* Channel : Connection->OpenChannels) { - if (Channel->Class == UActorChannel::StaticClass() && ((UActorChannel*)Channel)->Actor == Actor) { - return ((UActorChannel*)Channel); - } - } - - return nullptr; - } - - void Listen(UEngine* Engine, int Port) { - BaseAddress = (uintptr_t)GetModuleHandleA(nullptr); - - FName GameNetDriver = UKismetStringLibrary::Conv_StringToName(L"GameNetDriver"); - - std::cout << "Net driver create: " << (int)reinterpret_cast(BaseAddress + 0x371A5E0)(Engine, UWorld::GetWorld(), GameNetDriver, GameNetDriver) << std::endl; - - for (int i = 0; i < SDK::UObject::GObjects->Num(); i++) - { - SDK::UObject* Obj = SDK::UObject::GObjects->GetByIndex(i); - - if (!Obj) - continue; - - if (Obj->IsDefaultObject()) - continue; - - if (Obj->IsA(SDK::UNetDriver::StaticClass())) - { - NetDriver = (UNetDriver*)Obj; - break; - } - } - - std::cout << NetDriver->GetFullName() << std::endl; - - reinterpret_cast(BaseAddress + 0x3491890)(NetDriver, UWorld::GetWorld()); - - FURL url = FURL(); - - url.Port = Port; - - FString empy = FString(); - - std::cout << "Listen Status: " << (*(reinterpret_cast(*(__int64*)NetDriver + 0x280)))(NetDriver, (void*)UWorld::GetWorld()->NetworkNotify, &url, false, &empy) << std::endl; - - reinterpret_cast(BaseAddress + 0x3491890)(NetDriver, UWorld::GetWorld()); - - UWorld::GetWorld()->NetDriver = NetDriver; - } - - void TickNetworking() { - UWorld::GetWorld()->NetDriver = NetDriver; - - NetDriver->World = UWorld::GetWorld(); - - static FName name = FName(); - static bool nameInit = false; - - if (!nameInit) { - nameInit = true; - name = UKismetStringLibrary::Conv_StringToName(L"Actor"); - } - - ++ * (uint32_t*)((uintptr_t)NetDriver + 0x2AC); - - std::vector Actors = BuildConsiderList(UWorld::GetWorld(), NetDriver); - - for (UNetConnection* Connection : NetDriver->ClientConnections) { - if (!Connection->OwningActor || *(uint32_t*)((uintptr_t)Connection + 0x134) != 3) - continue; - - for (AActor* Actor : Actors) { - if ((Actor->IsA(APlayerController::StaticClass()))) { - if (Actor != Connection->OwningActor) { - continue; - } - else { - //*(uint8_t*)((uintptr_t)Actor + 0xA14) = 0x1; - Connection->ViewTarget = ((APlayerController*)Actor)->GetViewTarget(); - - if (!Connection->ViewTarget) - std::cout << "NULL VIEWTARGET BAD THINGS WILL HAPPEN" << std::endl; - - reinterpret_cast(BaseAddress + 0x359F9D0)((APlayerController*)Actor); - } - } - - // - - UActorChannel* ActorChannel = GetActorChannelForConnectionAndActor(Connection, Actor); - - if (!ActorChannel) { - ActorChannel = reinterpret_cast(BaseAddress + 0x3449E10)(Connection, &name, 1 << 1, -1); - - if (ActorChannel) { - reinterpret_cast(BaseAddress + 0x3283450)(ActorChannel, Actor, 0); - } - } - - if (ActorChannel && ActorChannel->Actor) { - if (!(*(int*)((uintptr_t)ActorChannel + 0x90) & 2u)) { - *(int*)((uintptr_t)ActorChannel + 0x90) |= 2u; - } - if (reinterpret_cast(BaseAddress + 0x327E860)(ActorChannel)) { - //std::cout << ActorChannel->Actor->GetFullName() << std::endl; - } - } - } - } - } -} \ No newline at end of file diff --git a/UndauntedInternalServer/Networking.h b/UndauntedInternalServer/Networking.h index 8d38d12..0ea65a5 100644 --- a/UndauntedInternalServer/Networking.h +++ b/UndauntedInternalServer/Networking.h @@ -1,14 +1,5 @@ #pragma once -#include -#include "SDK.hpp" - -using namespace SDK; - -namespace Networking { - extern UNetDriver* NetDriver; - - void Listen(UEngine* Engine, int Port); - - void TickNetworking(); -} \ No newline at end of file +// Compatibility include for modules that still include the historical root +// header. The implementation and public declarations now live in Networking/. +#include "Networking/Networking.h" diff --git a/UndauntedInternalServer/Networking/CombatBridge.inl b/UndauntedInternalServer/Networking/CombatBridge.inl new file mode 100644 index 0000000..f59ceaa --- /dev/null +++ b/UndauntedInternalServer/Networking/CombatBridge.inl @@ -0,0 +1,158 @@ +void* OrigProcessEvent = nullptr; + +struct PendingInterruptDamageContext { + PendingInterruptDamageContext* Previous = nullptr; + int32_t TargetIndex = -1; + int32_t TargetSerial = 0; + bool InterruptObserved = false; +}; + +thread_local PendingInterruptDamageContext* ActiveInterruptDamageContext = nullptr; + +void ProcessEventHook(UObject* Object, UFunction* Function, void* Parms) { + static UFunction* ServerTryActivateAbilityWithEventData = nullptr; + static UFunction* ServerTryActivateAbility = nullptr; + static UFunction* ServerNotifyLoadedWorld = nullptr; + static UFunction* ServerAcknowledgePossession = nullptr; + static UFunction* ServerTryDoDamage = nullptr; + static UFunction* ClientOnBehemothInterrupted = nullptr; + static bool AttemptedInterruptClientFunctionResolution = false; + + const auto CallOriginal = [&]() { + reinterpret_cast(OrigProcessEvent)(Object, Function, Parms); + }; + + if (!Object || !Function) { + ServerPerformance::RecordProcessEvent(false, true); + CallOriginal(); + return; + } + + // Gameplay interception only concerns server RPCs. The one passive client + // notification below uses an exact UFunction pointer resolved once from its + // already-loaded class, avoiding name work on the global ProcessEvent path. + constexpr uint32_t NetServerFunctionFlag = static_cast(EFunctionFlags::NetServer); + const bool IsNetServerFunction = (Function->FunctionFlags & NetServerFunctionFlag) != 0; + if (!IsNetServerFunction) { + constexpr uint32_t NetClientFunctionFlag = static_cast(EFunctionFlags::NetClient); + if (Globals::AmServer && + (Function->FunctionFlags & NetClientFunctionFlag) != 0 && + Object->IsA(Aplayer_state_bp_C::StaticClass())) { + if (!AttemptedInterruptClientFunctionResolution) { + AttemptedInterruptClientFunctionResolution = true; + ClientOnBehemothInterrupted = Aplayer_state_bp_C::StaticClass()->GetFunction( + "player_state_bp_C", "ClientOnBehemothInterrupted"); + } + if (Function == ClientOnBehemothInterrupted) { + Networking::NotifyBehemothInterruptClientRpc(); + if (ActiveInterruptDamageContext) + ActiveInterruptDamageContext->InterruptObserved = true; + } + } + ServerPerformance::RecordProcessEvent(false, true); + CallOriginal(); + return; + } + + const bool IsAbilityRpc = Object->IsA(UAbilitySystemComponent::StaticClass()); + const bool IsPlayerControllerRpc = Globals::AmServer && Object->IsA(APlayerController::StaticClass()); + const bool IsDamageRpc = Globals::AmServer && Object->IsA(UDamageComponent::StaticClass()); + if (!IsAbilityRpc && !IsPlayerControllerRpc && !IsDamageRpc) { + ServerPerformance::RecordProcessEvent(true, true); + CallOriginal(); + return; + } + + std::string FunctionName; + auto MatchesFunction = [&](UFunction*& CachedFunction, const char* ExpectedName) { + if (Function == CachedFunction) { + ServerPerformance::RecordProcessEventFunctionMatch(); + return true; + } + if (CachedFunction) + return false; + if (FunctionName.empty()) { + FunctionName = Function->GetName(); + ServerPerformance::RecordProcessEventNameLookup(true); + } + if (FunctionName != ExpectedName) + return false; + CachedFunction = Function; + ServerPerformance::RecordProcessEventFunctionMatch(); + return true; + }; + + if (IsAbilityRpc && MatchesFunction(ServerTryActivateAbilityWithEventData, + "ServerTryActivateAbilityWithEventData")) { + Params::AbilitySystemComponent_ServerTryActivateAbilityWithEventData* ActivateAbilityParams = (Params::AbilitySystemComponent_ServerTryActivateAbilityWithEventData*)Parms; + + if (ActivateAbilityParams && ServerTryActivateAbilityInternal((UAbilitySystemComponent*)Object, ActivateAbilityParams->AbilityToActivate, ActivateAbilityParams->InputPressed, ActivateAbilityParams->PredictionKey, &ActivateAbilityParams->TriggerEventData)) { + ServerPerformance::RecordAbilityRpc(true, false); + ServerPerformance::RecordProcessEvent(true, false); + return; + } + ServerPerformance::RecordAbilityRpc(false, true); + } + else if (IsAbilityRpc && MatchesFunction(ServerTryActivateAbility, "ServerTryActivateAbility")) { + Params::AbilitySystemComponent_ServerTryActivateAbility* ActivateAbilityParams = (Params::AbilitySystemComponent_ServerTryActivateAbility*)Parms; + + if (ActivateAbilityParams && ServerTryActivateAbilityInternal((UAbilitySystemComponent*)Object, ActivateAbilityParams->AbilityToActivate, ActivateAbilityParams->InputPressed, ActivateAbilityParams->PredictionKey, nullptr)) { + ServerPerformance::RecordAbilityRpc(true, false); + ServerPerformance::RecordProcessEvent(true, false); + return; + } + ServerPerformance::RecordAbilityRpc(false, true); + } + + const bool IsLoadedWorldNotification = IsPlayerControllerRpc && MatchesFunction( + ServerNotifyLoadedWorld, "ServerNotifyLoadedWorld"); + const bool IsPossessionAcknowledgement = IsPlayerControllerRpc && MatchesFunction( + ServerAcknowledgePossession, "ServerAcknowledgePossession"); + const bool IsDamageRpcMatch = IsDamageRpc && MatchesFunction( + ServerTryDoDamage, "ServerTryDoDamage"); + if (IsDamageRpcMatch) + Networking::NotifyDamageRpcObserved(); + const bool IsDamageNotification = IsDamageRpcMatch && Parms; + int32_t DamageTargetIndex = -1; + int32_t DamageTargetSerial = 0; + if (IsDamageNotification) { + const auto* DamageParams = static_cast(Parms); + // Copy the weak identity while the incoming parameter block is valid. + // Networking deliberately resolves index+serial itself after native + // validation/execution; generated FWeakObjectPtr::Get ignores serials. + DamageTargetIndex = DamageParams->Payload.Hit.Actor.ObjectIndex; + DamageTargetSerial = DamageParams->Payload.Hit.Actor.ObjectSerialNumber; + } + + ServerPerformance::RecordProcessEvent(true, true); + PendingInterruptDamageContext DamageContext{ + ActiveInterruptDamageContext, + DamageTargetIndex, + DamageTargetSerial, + false + }; + if (IsDamageNotification) + ActiveInterruptDamageContext = &DamageContext; + CallOriginal(); + if (IsDamageNotification) { + ActiveInterruptDamageContext = DamageContext.Previous; + if (DamageContext.InterruptObserved) { + Networking::NotifyUrgentDamageTarget( + DamageContext.TargetIndex, DamageContext.TargetSerial); + } + } + + if (!IsPlayerControllerRpc) + return; + + APlayerController* PlayerController = static_cast(Object); + if (IsLoadedWorldNotification && Parms) { + const auto* LoadedWorldParams = static_cast(Parms); + Networking::NotifyClientLoadedWorld(PlayerController, LoadedWorldParams->WorldPackageName); + } + else if (IsPossessionAcknowledgement && Parms) { + const auto* PossessionParams = static_cast(Parms); + Networking::NotifyClientAcknowledgedPawn(PlayerController, PossessionParams->P); + } + +} diff --git a/UndauntedInternalServer/Networking/NetworkLifecycle.cpp b/UndauntedInternalServer/Networking/NetworkLifecycle.cpp new file mode 100644 index 0000000..40456c6 --- /dev/null +++ b/UndauntedInternalServer/Networking/NetworkLifecycle.cpp @@ -0,0 +1,100 @@ +#include "NetworkLifecycle.h" + +#include +#include +#include + +namespace Networking::Lifecycle { + namespace { + LifecycleConfig Config{}; + std::atomic Observation{0}; + std::atomic HeartbeatSequence{0}; + uint32_t ObservationSequence = 0; + uint16_t WorldEpoch = 0; + SDK::UWorld* ObservedWorld = nullptr; + HANDLE Timer = nullptr; + + std::string Utf8(const std::wstring& Value) { + if (Value.empty()) return {}; + const int Size = WideCharToMultiByte(CP_UTF8, 0, Value.c_str(), -1, + nullptr, 0, nullptr, nullptr); + if (Size <= 1) return {}; + std::string Result(static_cast(Size - 1), '\0'); + WideCharToMultiByte(CP_UTF8, 0, Value.c_str(), -1, Result.data(), + Size, nullptr, nullptr); + return Result; + } + + void CALLBACK Heartbeat(void*, BOOLEAN) { + const uint64_t Packed = Observation.load(std::memory_order_acquire); + const uint32_t Sequence = static_cast(Packed >> 32); + if (Sequence == 0 || Config.callbackUrl.empty()) return; + + uint32_t HeartbeatId = HeartbeatSequence.fetch_add( + 1, std::memory_order_relaxed) + 1; + if (HeartbeatId == 0) { + HeartbeatSequence.store(1, std::memory_order_relaxed); + HeartbeatId = 1; + } + + const uint32_t Epoch = static_cast((Packed >> 16) & 0xffffu); + const uint32_t Raw = static_cast((Packed >> 8) & 0xffu); + const uint32_t Tracked = static_cast(Packed & 0xffu); + const std::string Body = "{\"id\":\"" + Utf8(Config.serverId) + + "\",\"port\":" + std::to_string(Config.port) + + ",\"pid\":" + std::to_string(GetCurrentProcessId()) + + ",\"liveConnections\":" + std::to_string(Tracked) + + ",\"rawConnections\":" + std::to_string(Raw) + + ",\"protocolVersion\":" + std::to_string(Config.protocolVersion) + + ",\"heartbeatSequence\":" + std::to_string(HeartbeatId) + + ",\"observationSequence\":" + std::to_string(Sequence) + + ",\"worldEpoch\":" + std::to_string(Epoch) + "}"; + + if (Config.callback != nullptr) + Config.callback(Config.callbackUrl, L"x-undaunted-lifecycle-token", + Config.callbackToken, Body); + } + } + + bool Start(const LifecycleConfig& NewConfig) { + Stop(); + Config = NewConfig; + if (Config.callbackUrl.empty()) return false; + return CreateTimerQueueTimer(&Timer, nullptr, Heartbeat, nullptr, 0, + 5000, WT_EXECUTEDEFAULT) != FALSE; + } + + void Stop() { + if (Timer != nullptr) { + DeleteTimerQueueTimer(nullptr, Timer, INVALID_HANDLE_VALUE); + Timer = nullptr; + } + Observation.store(0, std::memory_order_release); + HeartbeatSequence.store(0, std::memory_order_release); + ObservationSequence = 0; + WorldEpoch = 0; + ObservedWorld = nullptr; + } + + void ObserveConnections(uint32_t Tracked, uint32_t Raw, SDK::UWorld* World) { + if (ObservedWorld != World) { + ObservedWorld = World; + if (++WorldEpoch == 0) WorldEpoch = 1; + } + if (++ObservationSequence == 0) ObservationSequence = 1; + const uint64_t Packed = + (static_cast(ObservationSequence) << 32) | + (static_cast(WorldEpoch) << 16) | + (static_cast((std::min)(Raw, 128u)) << 8) | + static_cast((std::min)(Tracked, 128u)); + Observation.store(Packed, std::memory_order_release); + } + + void ResetWorld(SDK::UWorld* World) { + if (ObservedWorld == World) return; + ObservedWorld = World; + ObservationSequence = 0; + Observation.store(0, std::memory_order_release); + if (++WorldEpoch == 0) WorldEpoch = 1; + } +} diff --git a/UndauntedInternalServer/Networking/NetworkLifecycle.h b/UndauntedInternalServer/Networking/NetworkLifecycle.h new file mode 100644 index 0000000..9e7116e --- /dev/null +++ b/UndauntedInternalServer/Networking/NetworkLifecycle.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace SDK { + class UWorld; +} + +namespace Networking::Lifecycle { + using Callback = bool(*)(const std::wstring& Url, const std::wstring& TokenHeader, + const std::wstring& Token, const std::string& Body); + + struct LifecycleConfig { + std::wstring callbackUrl; + std::wstring callbackToken; + std::wstring serverId; + uint16_t port = 0; + uint32_t protocolVersion = 2; + Callback callback = nullptr; + }; + + bool Start(const LifecycleConfig& Config); + void Stop(); + void ObserveConnections(uint32_t Tracked, uint32_t Raw, SDK::UWorld* World); + void ResetWorld(SDK::UWorld* World); +} diff --git a/UndauntedInternalServer/Networking/Networking.cpp b/UndauntedInternalServer/Networking/Networking.cpp new file mode 100644 index 0000000..bbe1b93 --- /dev/null +++ b/UndauntedInternalServer/Networking/Networking.cpp @@ -0,0 +1,8 @@ +// Public networking facade. Implementation is split into private fragments so +// every subsystem shares one translation-unit RuntimeState without duplicated +// hook state or changed scheduling order. +#include "Private/RuntimeState.inl" +#include "Private/ConnectionState.inl" +#include "Private/ConsiderCache.inl" +#include "Private/ReplicationScheduler.inl" +#include "Private/ListenDispatch.inl" diff --git a/UndauntedInternalServer/Networking/Networking.h b/UndauntedInternalServer/Networking/Networking.h new file mode 100644 index 0000000..ad68377 --- /dev/null +++ b/UndauntedInternalServer/Networking/Networking.h @@ -0,0 +1,276 @@ +#pragma once + +#include +#include +#include +#include +#include "../SDK.hpp" + +using namespace SDK; + +namespace Networking { + using LifecycleEventSink = void(*)(const std::string& eventName, + const std::string& fieldsJson); + + constexpr size_t ReplicationBucketCount = 6; + constexpr size_t ConnectionBootstrapPhaseCount = 2; + constexpr size_t ImmediateMetricTrackedClassCount = 256; + constexpr size_t ImmediateMetricClassSlotCount = ImmediateMetricTrackedClassCount + 2; + constexpr size_t ImmediateNoDataBandCount = 6; + constexpr size_t ImmediateMetricClassNameCapacity = 64; + constexpr size_t NetFrequencyBandCount = 10; + constexpr size_t NetDormancyBandCount = 7; + constexpr size_t NetRoleCount = 5; + constexpr size_t NetRoleMatrixCount = NetRoleCount * NetRoleCount; + constexpr size_t PriorityBandCount = 5; + constexpr size_t CacheRebuildReasonCount = 6; + constexpr uint32_t ProfileSchemaVersion = 2; + + enum class UrgentReplicationReason : uint8_t { + Stagger, + Bleedout, + Revive, + Dodge, + Count + }; + constexpr size_t UrgentReplicationReasonCount = + static_cast(UrgentReplicationReason::Count); + + enum class CombatEvent : uint8_t { + BleedoutStarted, + ReviveCompleted, + ReviveQuickItem, + DodgeRequested, + DodgePerformed, + ComboStarted, + ComboNextMove, + ComboStateChanged, + ClientTryHit, + ServerAcceptHit, + ServerRefuseHit, + ProcessHitOnServer, + CombatTextQueued, + CombatTextMulticast, + StaggerConfirmed, + Count + }; + constexpr size_t CombatEventCount = static_cast(CombatEvent::Count); + + struct ImmediateClassCounters { + std::array ClassName{}; + uint64_t Attempts = 0; + uint64_t Successes = 0; + uint64_t ActorPasses = 0; + uint64_t ActorPassAttempts = 0; + uint64_t ActorPassSuccesses = 0; + uint64_t ActorPassesWithAnyData = 0; + uint64_t ActorPassesWithNoData = 0; + std::array NoDataPassStreaks{}; + uint64_t UntrackableOwnerCalls = 0; + uint64_t OwnerSensitiveDeferrals = 0; + uint64_t IrrelevantSkips = 0; + }; + + struct ProfilingCounters { + uint64_t SchedulerTickCalls = 0; + uint64_t ReplicationAttempts = 0; + uint64_t ReplicationSuccesses = 0; + uint64_t PreReplicationCalls = 0; + uint64_t PreReplicationUnavailable = 0; + uint64_t PreReplicationInvalidatedActors = 0; + uint64_t BootstrapReplicationAttempts = 0; + uint64_t BootstrapReplicationSuccesses = 0; + uint64_t UrgentDamageRpcMatches = 0; + uint64_t UrgentDamageInterruptCorrelations = 0; + uint64_t UrgentDamageWeakHandlesInvalid = 0; + uint64_t UrgentDamageDirectBehemothTargets = 0; + uint64_t UrgentDamageOwnerChainTargets = 0; + uint64_t UrgentDamageTargetsWithoutBehemoth = 0; + uint64_t UrgentDamageTargetsQueued = 0; + uint64_t UrgentDamageTargetsDeduplicated = 0; + uint64_t UrgentDamageTargetsDropped = 0; + uint64_t UrgentDamageTargetsInvalidated = 0; + uint64_t UrgentDamageTargetsExpired = 0; + uint64_t UrgentDamageSetupRetries = 0; + uint64_t UrgentDamageSetupRetryExhausted = 0; + uint64_t UrgentDamageReplicationAttempts = 0; + uint64_t UrgentDamageReplicationSuccesses = 0; + uint64_t UrgentDamageLatencyMilliseconds = 0; + uint64_t UrgentDamageLatencySamples = 0; + uint64_t BehemothInterruptClientNotifications = 0; + std::array UrgentQueuedByReason{}; + std::array UrgentDeduplicatedByReason{}; + std::array UrgentDroppedByReason{}; + std::array UrgentInvalidatedByReason{}; + std::array UrgentExpiredByReason{}; + std::array UrgentAttemptsByReason{}; + std::array UrgentSuccessesByReason{}; + std::array UrgentLatencyMillisecondsByReason{}; + std::array UrgentLatencySamplesByReason{}; + uint64_t CriticalPrepassCandidates = 0; + uint64_t CriticalPrepassAttempts = 0; + uint64_t CriticalPrepassSuccesses = 0; + uint64_t CriticalPrepassDuplicateSkips = 0; + uint64_t CriticalPrepassSetupFallbacks = 0; + uint64_t CriticalOwnedFrequencyLimited = 0; + std::array CombatEventCalls{}; + std::array CombatEventSuccesses{}; + uint64_t CombatTextEntries = 0; + uint64_t CombatTextEstimatedBytes = 0; + uint64_t CombatTextMulticastEntries = 0; + uint64_t CombatTextMulticastEstimatedBytes = 0; + std::array ReplicationAttemptsByBucket{}; + std::array ReplicationSuccessesByBucket{}; + std::array ImmediateClasses{}; + std::array BootstrapPhaseConnections{}; + uint64_t CacheRebuilds = 0; + std::array CacheRebuildsByReason{}; + uint64_t CacheRebuildMicroseconds = 0; + uint64_t CacheRebuildTimingSamples = 0; + uint64_t CandidateSelectionMicroseconds = 0; + uint64_t CandidateSelectionTimingSamples = 0; + uint64_t ChannelScanMicroseconds = 0; + uint64_t ChannelScanTimingSamples = 0; + uint64_t ReplicationMicroseconds = 0; + uint64_t ReplicationTimingSamples = 0; + uint64_t ZeroConnectionTicks = 0; + uint64_t ConnectionSamples = 0; + uint64_t ConnectionTotal = 0; + uint64_t NativeDispatchCalls = 0; + uint64_t NativeDispatchOwnershipValidBefore = 0; + uint64_t NativeDispatchOwnershipValidAfter = 0; + uint64_t NativeDispatchOwnershipLostInside = 0; + uint64_t NativeFlushCalls = 0; + uint64_t NativeFlushOwnershipValidBefore = 0; + uint64_t NativeFlushOwnershipValidAfter = 0; + uint64_t NativeFlushOwnershipLostInside = 0; + uint64_t NativeOwnershipMissingAfterEngineTick = 0; + uint64_t NativeReciprocalRepairs = 0; + uint64_t NativeDispatchBlocked = 0; + uint64_t NativeDispatchConflicts = 0; + uint64_t NativePostLogins = 0; + uint64_t LoadedWorldAccepted = 0; + uint64_t LoadedWorldMismatched = 0; + uint64_t PossessionAcknowledged = 0; + uint64_t BootstrapStarts = 0; + uint64_t BootstrapRestarts = 0; + uint64_t BootstrapCompleted = 0; + uint64_t BootstrapDeadlines = 0; + uint64_t CriticalActorsDiscovered = 0; + uint64_t CriticalChannelsAcknowledged = 0; + uint64_t ActorChannelsCreated = 0; + uint64_t ActorChannelsReused = 0; + uint64_t ActorPropertySamples = 0; + std::array NetUpdateFrequencyBands{}; + std::array MinNetUpdateFrequencyBands{}; + std::array NetDormancyBands{}; + std::array NetRoleMatrix{}; + uint64_t OwnedActorSamples = 0; + uint64_t ReplicatedMovementSamples = 0; + uint64_t CriticalActorSamples = 0; + uint64_t ImmediateActorSamples = 0; + uint64_t NetTemporarySamples = 0; + uint64_t NetStartupSamples = 0; + uint64_t NetLoadOnClientSamples = 0; + uint64_t OnlyRelevantToOwnerSamples = 0; + uint64_t AlwaysRelevantSamples = 0; + uint64_t OwnerRelevancySamples = 0; + uint64_t TearOffSamples = 0; + uint64_t ExcludedNotReplicated = 0; + uint64_t ExcludedRemoteRoleNone = 0; + uint64_t ExcludedLocalRoleNone = 0; + uint64_t UnexpectedLocalRole = 0; + uint64_t ExcludedDestroying = 0; + uint64_t ExcludedWrongWorld = 0; + uint64_t ChannelStateSamples = 0; + uint64_t ChannelOpenAcknowledgedSamples = 0; + uint64_t ChannelNotOpenAcknowledgedSamples = 0; + uint64_t LivePolicyEligible = 0; + uint64_t LivePolicyDue = 0; + uint64_t LivePolicyNotDue = 0; + uint64_t LivePolicyRelevant = 0; + uint64_t LivePolicyNotRelevant = 0; + uint64_t LivePolicyDormantDeferrals = 0; + uint64_t LivePolicyDriverMismatches = 0; + uint64_t LivePolicyTemporaryRetirements = 0; + uint64_t LivePolicyTearOffRetirements = 0; + uint64_t LivePolicyIdentityFallbacks = 0; + uint64_t LivePolicyActorIdentityFallbacks = 0; + uint64_t LivePolicyConnectionIdentityFallbacks = 0; + uint64_t LivePolicyStateCapacityFallbacks = 0; + uint64_t LivePolicyUntrackableOwners = 0; + uint64_t LivePolicyOwnerSensitiveDeferrals = 0; + uint64_t LivePolicyNativeRelevancyCalls = 0; + uint64_t LivePolicyNativeRelevancyFallbacks = 0; + uint64_t LivePolicyRelevancyAudits = 0; + uint64_t LivePolicyRelevancyRecheckMilliseconds = 0; + uint64_t LivePolicyRelevancyRecheckSamples = 0; + uint64_t LivePolicyIrrelevantSkips = 0; + uint64_t LivePolicyLevelInitializationUnavailable = 0; + uint64_t LivePolicyPrioritySorts = 0; + uint64_t LivePolicyPriorityCandidates = 0; + std::array LivePolicyPriorityBands{}; + uint64_t LivePolicyMovementPrepassAttempts = 0; + uint64_t LivePolicyMovementPrepassSuccesses = 0; + uint64_t LivePolicyCriticalRejected = 0; + uint64_t LivePolicyDuplicateSkips = 0; + uint64_t SchedulerStateInsertions = 0; + uint64_t SchedulerStateCapacityDrops = 0; + uint64_t SchedulerActorIdentityResets = 0; + uint64_t SchedulerOwnerChanges = 0; + uint64_t SchedulerStatePrunes = 0; + uint64_t LoadedWorldLatencyMilliseconds = 0; + uint64_t LoadedWorldLatencySamples = 0; + uint64_t BootstrapLatencyMilliseconds = 0; + uint64_t BootstrapLatencySamples = 0; + uint64_t DroppedLifecycleEvents = 0; + uint32_t MaximumConnections = 0; + uint32_t CurrentCandidates = 0; + uint32_t MaximumCandidates = 0; + uint32_t CurrentSchedulerStates = 0; + uint32_t MaximumSchedulerStates = 0; + uint32_t LastCompletedPhase = 0; + }; + + extern UNetDriver* NetDriver; + + void Configure(uint32_t considerCacheMaxAgeMilliseconds); + void ConfigureLifecycleEventSink(LifecycleEventSink sink); + void BeginFrameNetworkLifecycle(UWorld* World); + void EndFrameNetworkLifecycle(UWorld* World); + void ResetWorldState(UWorld* World); + void ResetDriverState(UNetDriver* Driver); + + // Compatibility overloads keep the networking category independently + // buildable before the later engine-integration patch is applied. + bool Listen(UEngine* Engine, int Port); + bool Listen(UEngine* Engine, UWorld* World, int Port); + const std::string& GetLastListenError(); + + void TickNetworking(); + void TickNetworking(UWorld* World); + // Called immediately before the native UIpNetDriver transport dispatch pass. + // Synthetic startup can briefly leave only one side of the UWorld/NetDriver + // ownership pair populated; this restores a coherent pair before packets are + // allowed to reach FNetworkNotify. + bool PrepareNativeNetworkDispatch(UNetDriver* Driver); + void NotifyNativeNetworkDispatchCompleted(UNetDriver* Driver); + bool PrepareNativeNetworkFlush(UNetDriver* Driver); + void NotifyNativeNetworkFlushCompleted(UNetDriver* Driver); + void NotifyNativeEngineTickCompleted(UWorld* World); + void NotifyNativePostLogin(APlayerController* PlayerController); + void NotifyClientLoadedWorld(APlayerController* PlayerController, FName WorldPackageName); + void NotifyClientAcknowledgedPawn(APlayerController* PlayerController, APawn* Pawn); + void NotifyDamageRpcObserved(); + void NotifyUrgentDamageTarget(int32_t objectIndex, int32_t objectSerialNumber); + void NotifyBehemothInterruptClientRpc(); + void QueueUrgentActor(AActor* actor, UrgentReplicationReason reason, + APlayerController* targetController = nullptr); + void RecordCombatEvent(CombatEvent event, bool succeeded = false, uint32_t amount = 1); + void RecordCombatTextQueue(const FArchonMultitypeCombatTextEntry& entry); + void RecordCombatTextMulticast(uint32_t entryCount); + + const std::vector& GetLiveConnections(); + + ProfilingCounters TakeProfilingCounters(); +} diff --git a/UndauntedInternalServer/Networking/Private/ConnectionState.inl b/UndauntedInternalServer/Networking/Private/ConnectionState.inl new file mode 100644 index 0000000..e42e0fc --- /dev/null +++ b/UndauntedInternalServer/Networking/Private/ConnectionState.inl @@ -0,0 +1,374 @@ + static void ResetConsiderCache() { + ConsiderCache.clear(); + CandidateActorLookup.clear(); + PriorityScratch.clear(); + CachedLevelActorCounts.clear(); + ActorReplicationStates.clear(); + ConnectionBootstrapStates.clear(); + ConnectionScheduleStates.clear(); + CurrentSchedulerActorStates = 0; + CurrentConnections.clear(); + LiveConnections.clear(); + PendingConnectionEventCount = 0; + PendingUrgentDamageTargetCount = 0; + CurrentConnectionPass = 0; + CachedWorld = nullptr; + CachedWorldIdentity = {}; + LastConsiderCacheBuildMs = 0; + ConsiderCacheDirtyForNextConnection = true; + } + + static void QueueConnectionEvent(const ConnectionEvent& Event) { + if (PendingConnectionEventCount < PendingConnectionEvents.size()) { + PendingConnectionEvents[PendingConnectionEventCount++] = Event; + } + else { + ++ProfileCounters.DroppedLifecycleEvents; + } + } + + static void EmitPendingConnectionEvents() { + for (size_t Index = 0; Index < PendingConnectionEventCount; ++Index) { + const ConnectionEvent& Event = PendingConnectionEvents[Index]; + const std::string CommonFields = + "\"connection\":" + PointerValue(Event.Connection) + + ",\"networkTick\":" + std::to_string(Event.NetworkTick) + + ",\"valueA\":" + std::to_string(Event.ValueA) + + ",\"valueB\":" + std::to_string(Event.ValueB) + + ",\"elapsedMs\":" + std::to_string(Event.ElapsedMilliseconds); + + if (Event.Type == ConnectionEventType::Open) { + RecordLifecycleEvent("connection_open", CommonFields); + } + else if (Event.Type == ConnectionEventType::Close) { + RecordLifecycleEvent("connection_close", CommonFields); + } + else if (Event.Type == ConnectionEventType::BootstrapStarted) { + RecordLifecycleEvent("connection_bootstrap_started", CommonFields); + } + else if (Event.Type == ConnectionEventType::BootstrapRestarted) { + RecordLifecycleEvent("connection_bootstrap_restarted", CommonFields); + } + else if (Event.Type == ConnectionEventType::LoadedWorldAccepted) { + RecordLifecycleEvent("loaded_world_accepted", CommonFields); + } + else if (Event.Type == ConnectionEventType::LoadedWorldMismatch) { + RecordLifecycleEvent("loaded_world_mismatch", CommonFields); + } + else if (Event.Type == ConnectionEventType::PossessionAcknowledged) { + RecordLifecycleEvent("possession_acknowledged", CommonFields); + } + else if (Event.Type == ConnectionEventType::CriticalActorDiscovered) { + RecordLifecycleEvent("bootstrap_critical_actor_discovered", CommonFields + + ",\"role\":\"" + InitializationRoleName(Event.ValueA) + "\""); + } + else if (Event.Type == ConnectionEventType::CriticalChannelAcknowledged) { + RecordLifecycleEvent("bootstrap_critical_channel_acknowledged", CommonFields + + ",\"role\":\"" + InitializationRoleName(Event.ValueA) + "\""); + } + else if (Event.Type == ConnectionEventType::BootstrapCompleted) { + RecordLifecycleEvent("connection_bootstrap_completed", CommonFields); + } + else if (Event.Type == ConnectionEventType::BootstrapDeadline) { + RecordLifecycleEvent("connection_bootstrap_deadline", CommonFields); + } + else { + const char* EventName = "native_unknown"; + switch (Event.Type) { + case ConnectionEventType::NativePostLogin: + EventName = "native_post_login"; break; + case ConnectionEventType::NativeReciprocalRepair: + EventName = "native_reciprocal_repair"; break; + case ConnectionEventType::NativeDispatchConflict: + EventName = "native_dispatch_conflict"; break; + default: + break; + } + RecordLifecycleEvent(EventName, CommonFields); + } + } + PendingConnectionEventCount = 0; + } + + static void RefreshConnectionState(UNetDriver* Driver, uint32_t CurrentTick) { + CurrentConnections.clear(); + LiveConnections.clear(); + + if (!Driver) + return; + + CurrentConnections.reserve(Driver->ClientConnections.Num()); + LiveConnections.reserve(Driver->ClientConnections.Num()); + + for (UNetConnection* Connection : Driver->ClientConnections) { + if (!IsOpenConnection(Connection)) + continue; + + CurrentConnections.insert(Connection); + LiveConnections.push_back(Connection); + + auto ScheduleIt = ConnectionScheduleStates.find(Connection); + if (ScheduleIt == ConnectionScheduleStates.end()) { + ConnectionScheduleStates.emplace(Connection, + MakeConnectionScheduleState(Connection, Driver->World)); + } + else if (!IdentityMatches(ScheduleIt->second.Connection, Connection) || + !IdentityMatches(ScheduleIt->second.World, Driver->World)) { + const uint32_t Removed = static_cast(ScheduleIt->second.Actors.size()); + CurrentSchedulerActorStates = Removed <= CurrentSchedulerActorStates + ? CurrentSchedulerActorStates - Removed + : 0; + ScheduleIt->second = MakeConnectionScheduleState(Connection, Driver->World); + } + else { + const ObjectIdentity ViewTargetIdentity = + TrackObject(Connection->ViewTarget); + if (!IdentitiesEqual(ScheduleIt->second.ViewTarget, + ViewTargetIdentity)) { + ScheduleIt->second.ViewTarget = ViewTargetIdentity; + for (auto& [Actor, State] : ScheduleIt->second.Actors) + State.NextRelevancyCheckMs = 0; + } + } + + auto StateIt = ConnectionBootstrapStates.find(Connection); + if (StateIt == ConnectionBootstrapStates.end()) { + ConnectionBootstrapStates.emplace(Connection, + MakeConnectionBootstrapState(Connection, Driver->World)); + ++ProfileCounters.BootstrapStarts; + QueueConnectionEvent({ ConnectionEventType::Open, Connection, CurrentTick, + static_cast(Driver->ClientConnections.Num()), + static_cast(LiveConnections.size()), 0 }); + QueueConnectionEvent({ ConnectionEventType::BootstrapStarted, Connection, CurrentTick, + 0, 0, 0 }); + continue; + } + + APlayerController* CurrentController = Connection->PlayerController; + if (!IdentityMatches(StateIt->second.Connection, Connection) || + !IdentityMatches(StateIt->second.World, Driver->World) || + !IdentityMatches(StateIt->second.Controller, CurrentController)) { + StateIt->second = MakeConnectionBootstrapState(Connection, Driver->World); + ++ProfileCounters.BootstrapRestarts; + QueueConnectionEvent({ ConnectionEventType::BootstrapRestarted, Connection, CurrentTick, + CurrentController ? 1u : 0u, 0, 0 }); + } + } + + for (auto It = ConnectionBootstrapStates.begin(); It != ConnectionBootstrapStates.end();) { + if (!CurrentConnections.contains(It->first)) { + QueueConnectionEvent({ ConnectionEventType::Close, It->first, CurrentTick, 0, 0, 0 }); + ClearConnectionScheduleState(It->first); + It = ConnectionBootstrapStates.erase(It); + } + else { + ++It; + } + } + } + + static UWorld* GetActorWorld(AActor* Actor) { + if (!Actor || !Actor->VTable) + return nullptr; + + return reinterpret_cast(*(void**)((uintptr_t)Actor->VTable + OffsetActorGetWorldVTable))(Actor); + } + + static bool IsActorReplicationCandidate(UWorld* World, AActor* Actor) { + return World + && Actor + && Actor->Class + && Actor->bReplicates + && Actor->RemoteRole != ENetRole::ROLE_None + && Actor->Role != ENetRole::ROLE_None + && !Actor->bActorIsBeingDestroyed + && (!NetDriver || Actor->NetDriverName.IsNone() || + Actor->NetDriverName == NetDriver->NetDriverName) + && GetActorWorld(Actor) == World; + } + + static __forceinline bool IsCachedActorStillReplicatable(AActor* Actor) { + return Actor + && Actor->Class + && Actor->bReplicates + && Actor->RemoteRole != ENetRole::ROLE_None + && Actor->Role != ENetRole::ROLE_None + && !Actor->bActorIsBeingDestroyed; + } + + static int CountWorldActors(UWorld* World) { + int TotalActorCount = 0; + + for (ULevel* Level : World->Levels) { + if (Level) + TotalActorCount += Level->Actors.Num(); + } + + return TotalActorCount; + } + + static bool GetCacheRebuildReason( + UWorld* World, + CacheRebuildReason& Reason + ) { + if (!World) + return false; + + if (CachedWorld != World) { + Reason = CacheRebuildReason::WorldChanged; + return true; + } + + if (!IsStableIdentity(CachedWorldIdentity, CachedWorld)) { + Reason = CacheRebuildReason::WorldIdentity; + return true; + } + + if (GetTickCount64() - LastConsiderCacheBuildMs >= ConsiderCacheMaxAgeMs) { + Reason = CacheRebuildReason::MaximumAge; + return true; + } + + if (CachedLevelActorCounts.size() != World->Levels.Num()) { + Reason = CacheRebuildReason::LevelCount; + return true; + } + + for (int i = 0; i < World->Levels.Num(); i++) { + ULevel* Level = World->Levels[i]; + int ActorCount = Level ? Level->Actors.Num() : -1; + + if (CachedLevelActorCounts[i] != ActorCount) { + Reason = CacheRebuildReason::ActorCount; + return true; + } + } + + return false; + } + + static __forceinline float VectorDistanceSquared(const FVector& A, const FVector& B) { + float X = A.X - B.X; + float Y = A.Y - B.Y; + float Z = A.Z - B.Z; + + return X * X + Y * Y + Z * Z; + } + + static __forceinline float RotationDelta(const FRotator& A, const FRotator& B) { + float Pitch = A.Pitch > B.Pitch ? A.Pitch - B.Pitch : B.Pitch - A.Pitch; + float Yaw = A.Yaw > B.Yaw ? A.Yaw - B.Yaw : B.Yaw - A.Yaw; + float Roll = A.Roll > B.Roll ? A.Roll - B.Roll : B.Roll - A.Roll; + float MaxPitchYaw = Pitch > Yaw ? Pitch : Yaw; + + return MaxPitchYaw > Roll ? MaxPitchYaw : Roll; + } + + static __forceinline TransformSample SampleActorTransform(AActor* Actor) { + TransformSample Sample{}; + + if (Actor->RootComponent) { + Sample.Location = Actor->RootComponent->RelativeLocation; + Sample.Rotation = Actor->RootComponent->RelativeRotation; + } + else { + Sample.Location = Actor->ReplicatedMovement.Location; + Sample.Rotation = Actor->ReplicatedMovement.Rotation; + } + + Sample.Valid = true; + return Sample; + } + + static __forceinline bool HasAnyMovementSignal(AActor* Actor, const TransformSample& Sample) { + return !Sample.Location.IsZero() + || !Actor->ReplicatedMovement.LinearVelocity.IsZero() + || !Actor->ReplicatedMovement.AngularVelocity.IsZero(); + } + + static uint32_t ClassifyReplicationClass( + AActor* Actor, + const ReplicationClassCache& ClassCache + ) { + uint32_t Flags = 0; + if (Actor->IsA(ClassCache.PlayerController)) Flags |= ClassPlayerController; + if (Actor->IsA(ClassCache.ArchonCharacter)) Flags |= ClassArchonCharacter; + if (Actor->IsA(ClassCache.ArchonBehemoth)) Flags |= ClassArchonBehemoth; + if (Actor->IsA(ClassCache.ArchonLantern)) Flags |= ClassArchonLantern; + if (Actor->IsA(ClassCache.Projectile)) Flags |= ClassProjectile; + if (Actor->IsA(ClassCache.Pawn)) Flags |= ClassPawn; + if (Actor->IsA(ClassCache.ArchonEquipment)) Flags |= ClassArchonEquipment; + if (Actor->IsA(ClassCache.ArchonLoadout)) Flags |= ClassArchonLoadout; + if (Actor->IsA(ClassCache.ArchonAoe)) Flags |= ClassArchonAoe; + if (Actor->IsA(ClassCache.ArchonBeam)) Flags |= ClassArchonBeam; + if (Actor->IsA(ClassCache.AbilityActor)) Flags |= ClassAbilityActor; + return Flags; + } + + static ReplicationBucket ClassifyReplicationBucket( + AActor* Actor, + uint32_t ClassFlags, + const TransformSample& Sample + ) { + if ((ClassFlags & (ClassPlayerController | ClassArchonLantern)) != 0 || + !Actor->bReplicateMovement) + return ReplicationBucket::Immediate; + + if ((ClassFlags & ClassArchonCharacter) != 0) + return ReplicationBucket::ArchonCharacter; + + if ((ClassFlags & ClassArchonBehemoth) != 0) + return ReplicationBucket::ArchonBehemoth; + + if ((ClassFlags & ClassProjectile) != 0) + return ReplicationBucket::Projectile; + + if ((ClassFlags & ClassPawn) != 0) + return ReplicationBucket::PawnOrCharacter; + + return HasAnyMovementSignal(Actor, Sample) + ? ReplicationBucket::MovementActor + : ReplicationBucket::Immediate; + } + + static bool IsCriticalReplicationActor(uint32_t ClassFlags) { + if (!kAlwaysReplicateCriticalActors) + return false; + + return (ClassFlags & (ClassPlayerController | + ClassArchonCharacter | ClassArchonLantern)) != 0; + } + + static bool IsLatencySensitiveOwnedActor(uint32_t ClassFlags) { + return (ClassFlags & ( + ClassArchonEquipment | + ClassArchonLoadout | + ClassArchonAoe | + ClassArchonBeam | + ClassAbilityActor)) != 0; + } + + static bool IsChannelOpenAcknowledged(UActorChannel* Channel); + static uint32_t CurrentNetworkTick(); + + static void PruneReplicationState(UWorld* World) { + std::erase_if(ActorReplicationStates, + [World](const auto& Entry) { + return !IsStableIdentity(Entry.second.Identity, Entry.first) || + !IsActorReplicationCandidate(World, Entry.first); + }); + } + + static void PruneSchedulerState(UWorld* World) { + for (auto& [Connection, Schedule] : ConnectionScheduleStates) { + const size_t Removed = std::erase_if(Schedule.Actors, + [World](const auto& Entry) { + return !IsStableIdentity(Entry.second.Actor, Entry.first) || + !IsActorReplicationCandidate(World, Entry.first); + }); + CurrentSchedulerActorStates = Removed <= CurrentSchedulerActorStates + ? CurrentSchedulerActorStates - static_cast(Removed) + : 0; + ProfileCounters.SchedulerStatePrunes += Removed; + } +} diff --git a/UndauntedInternalServer/Networking/Private/ConsiderCache.inl b/UndauntedInternalServer/Networking/Private/ConsiderCache.inl new file mode 100644 index 0000000..abd0ad9 --- /dev/null +++ b/UndauntedInternalServer/Networking/Private/ConsiderCache.inl @@ -0,0 +1,708 @@ + static void RebuildConsiderCache( + UWorld* World, + UNetDriver* Driver, + const ReplicationClassCache& ClassCache, + CacheRebuildReason Reason + ) { + const int64_t TimingStartedAt = PerformanceTimestamp(); + const bool CaptureActorPropertyDistribution = ActorPropertyDistributionDirty; + ++ProfileCounters.CacheRebuilds; + ++ProfileCounters.CacheRebuildsByReason[static_cast(Reason)]; + if (!World || !Driver) { + ResetConsiderCache(); + ProfileCounters.CacheRebuildMicroseconds += ElapsedMicroseconds(TimingStartedAt); + ++ProfileCounters.CacheRebuildTimingSamples; + return; + } + + int TotalActorCount = CountWorldActors(World); + + ConsiderCache.clear(); + CandidateActorLookup.clear(); + CachedLevelActorCounts.clear(); + ConsiderCache.reserve(TotalActorCount); + CandidateActorLookup.reserve(static_cast(TotalActorCount)); + if (PriorityScratch.capacity() < static_cast(TotalActorCount)) + PriorityScratch.reserve(TotalActorCount); + ActorReplicationStates.reserve(TotalActorCount); + CachedLevelActorCounts.reserve(World->Levels.Num()); + PruneReplicationState(World); + PruneSchedulerState(World); + for (auto& [Connection, Schedule] : ConnectionScheduleStates) { + if (Schedule.Actors.bucket_count() < ConsiderCache.capacity()) + Schedule.Actors.reserve((std::min)( + MaximumScheduledActorsPerConnection, ConsiderCache.capacity())); + } + + for (ULevel* Level : World->Levels) { + CachedLevelActorCounts.push_back(Level ? Level->Actors.Num() : -1); + + if (!Level) + continue; + + for (AActor* Actor : Level->Actors) { + if (!Actor || !Actor->Class) + continue; + if (CaptureActorPropertyDistribution) { + const size_t LocalRole = (std::min)( + static_cast(Actor->Role), NetRoleCount - 1); + const size_t RemoteRole = (std::min)( + static_cast(Actor->RemoteRole), NetRoleCount - 1); + ++ProfileCounters.NetRoleMatrix[ + LocalRole * NetRoleCount + RemoteRole]; + } + if (!Actor->bReplicates) { + if (CaptureActorPropertyDistribution) + ++ProfileCounters.ExcludedNotReplicated; + continue; + } + if (Actor->RemoteRole == ENetRole::ROLE_None) { + if (CaptureActorPropertyDistribution) + ++ProfileCounters.ExcludedRemoteRoleNone; + continue; + } + if (Actor->Role == ENetRole::ROLE_None) { + if (CaptureActorPropertyDistribution) + ++ProfileCounters.ExcludedLocalRoleNone; + continue; + } + if (CaptureActorPropertyDistribution && + Actor->Role != ENetRole::ROLE_Authority) + ++ProfileCounters.UnexpectedLocalRole; + if (Actor->bActorIsBeingDestroyed) { + if (CaptureActorPropertyDistribution) + ++ProfileCounters.ExcludedDestroying; + continue; + } + if (GetActorWorld(Actor) != World) { + if (CaptureActorPropertyDistribution) + ++ProfileCounters.ExcludedWrongWorld; + continue; + } + if (!Actor->NetDriverName.IsNone() && + Actor->NetDriverName != Driver->NetDriverName) { + ++ProfileCounters.LivePolicyDriverMismatches; + continue; + } + + TransformSample Sample = SampleActorTransform(Actor); + const uint32_t ClassFlags = + ClassifyReplicationClass(Actor, ClassCache); + const bool IsPlayerController = + (ClassFlags & ClassPlayerController) != 0; + ReplicationBucket Bucket = + ClassifyReplicationBucket(Actor, ClassFlags, Sample); + const bool IsCritical = + IsCriticalReplicationActor(ClassFlags); + if (CaptureActorPropertyDistribution) + RecordActorPropertyDistribution(Actor, Bucket, IsCritical); + const ObjectIdentity ActorIdentity = TrackObject(Actor); + if (ResolveObject(ActorIdentity) != Actor) { + ++ProfileCounters.SchedulerActorIdentityResets; + ConsiderCacheDirtyForNextConnection = true; + continue; + } + auto [StateIt, StateInserted] = ActorReplicationStates.try_emplace(Actor); + ActorReplicationState& State = StateIt->second; + if (!StateInserted && (!IsStableIdentity(ActorIdentity, Actor) || + !IdentitiesEqual(State.Identity, ActorIdentity))) { + State = {}; + ++ProfileCounters.SchedulerActorIdentityResets; + } + State.Identity = ActorIdentity; + + ConsiderCache.push_back({ Actor, Bucket, ClassFlags, IsPlayerController, + IsCritical, &State }); + CandidateActorLookup[Actor] = &ConsiderCache.back(); + } + } + + CachedWorld = World; + CachedWorldIdentity = TrackObject(World); + // Rebuild after the vector is complete; vector growth invalidates the + // addresses stored in the urgent candidate lookup. + CandidateActorLookup.clear(); + CandidateActorLookup.reserve(ConsiderCache.size()); + for (ReplicationCandidate& Candidate : ConsiderCache) + CandidateActorLookup[Candidate.Actor] = &Candidate; + LastConsiderCacheBuildMs = GetTickCount64(); + ProfileCounters.CurrentCandidates = static_cast(ConsiderCache.size()); + ProfileCounters.MaximumCandidates = (std::max)(ProfileCounters.MaximumCandidates, + ProfileCounters.CurrentCandidates); + ProfileCounters.CacheRebuildMicroseconds += ElapsedMicroseconds(TimingStartedAt); + ++ProfileCounters.CacheRebuildTimingSamples; + ActorPropertyDistributionDirty = false; + } + + static void BuildReplicateList(uint32_t CurrentTick) { + for (ReplicationCandidate& Candidate : ConsiderCache) { + AActor* Actor = Candidate.Actor; + + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Actor) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + continue; + } + if (!IsCachedActorStillReplicatable(Actor)) + continue; + + Candidate.Bucket = ClassifyReplicationBucket( + Actor, Candidate.ClassFlags, SampleActorTransform(Actor)); + if (Candidate.Bucket == ReplicationBucket::Immediate) { + ActorReplicationState& State = *Candidate.ReplicationState; + if (State.ImmediatePassNetworkTick != CurrentTick) { + State.ImmediatePassNetworkTick = CurrentTick; + State.ImmediatePassAttempts = 0; + State.ImmediatePassSuccesses = 0; + } + } + + } + } + + static void FinalizeImmediatePassMetrics(uint32_t CurrentTick) { + for (ReplicationCandidate& Candidate : ConsiderCache) { + if (Candidate.Bucket != ReplicationBucket::Immediate || !Candidate.ReplicationState) + continue; + + ActorReplicationState& State = *Candidate.ReplicationState; + if (State.ImmediatePassNetworkTick != CurrentTick || + State.ImmediatePassFinalizedTick == CurrentTick || + State.ImmediatePassAttempts == 0) { + continue; + } + + uint16_t MetricSlot = ResolveImmediateMetricSlot( + Candidate.Actor, Candidate.ReplicationState); + if (MetricSlot >= ProfileCounters.ImmediateClasses.size()) + MetricSlot = ImmediateMetricInvalidSlot; + ImmediateClassCounters& Counters = ProfileCounters.ImmediateClasses[MetricSlot]; + ++Counters.ActorPasses; + Counters.ActorPassAttempts += State.ImmediatePassAttempts; + Counters.ActorPassSuccesses += State.ImmediatePassSuccesses; + + if (State.ImmediatePassSuccesses > 0) { + ++Counters.ActorPassesWithAnyData; + State.ConsecutiveImmediateNoDataPasses = 0; + } + else { + ++Counters.ActorPassesWithNoData; + if (State.ConsecutiveImmediateNoDataPasses < + (std::numeric_limits::max)()) { + ++State.ConsecutiveImmediateNoDataPasses; + } + ++Counters.NoDataPassStreaks[ + ImmediateNoDataBand(State.ConsecutiveImmediateNoDataPasses)]; + } + State.ImmediatePassFinalizedTick = CurrentTick; + } + } + + static UNetDriver* FindNetDriverByName(UEngine* Engine, UWorld* World, + const FName& DriverName) { + if (!Engine || !World || BaseAddress == 0) + return nullptr; + FindNamedNetDriverFn FindNamedNetDriver = + reinterpret_cast(BaseAddress + OffsetFindNamedNetDriver); + return FindNamedNetDriver(Engine, World, DriverName); + } + + static bool IsOwnedByConnection( + UWorld* World, + UNetConnection* Connection, + AActor* Actor + ) { + if (!World || !Connection || !Actor) + return false; + + APlayerController* Controller = Connection->PlayerController; + AActor* Pawn = Controller ? Controller->Pawn : nullptr; + AActor* PlayerState = Controller ? Controller->PlayerState : nullptr; + AActor* Current = Actor; + for (size_t Depth = 0; Current && Depth <= MaximumOwnerChainDepth; ++Depth) { + if (Current == Connection->OwningActor || Current == Connection->ViewTarget || + Current == Controller || Current == Pawn || Current == PlayerState) { + return true; + } + + const ObjectIdentity Identity = TrackObject(Current); + if (ResolveObject(Identity) != Current || + Current->bActorIsBeingDestroyed || + GetActorWorld(Current) != World) { + return false; + } + Current = Current->Owner; + } + return false; + } + + static bool IsDirectConnectionActor( + UNetConnection* Connection, + AActor* Actor + ) { + if (!Connection || !Actor) + return false; + APlayerController* Controller = Connection->PlayerController; + return Actor == Connection->OwningActor || + Actor == Connection->ViewTarget || + Actor == Controller || + (Controller && + (Actor == Controller->Pawn || Actor == Controller->PlayerState)); + } + + static ActorScheduleState* ResolveSchedulingState( + UWorld* World, + UNetConnection* Connection, + AActor* Actor, + ActorReplicationState* ReplicationState + ) { + if (!World || !Connection || !Actor || !ReplicationState || + ResolveObject(ReplicationState->Identity) != Actor) { + ++ProfileCounters.LivePolicyActorIdentityFallbacks; + return nullptr; + } + + auto ConnectionIt = ConnectionScheduleStates.find(Connection); + if (ConnectionIt == ConnectionScheduleStates.end() || + !IsStableIdentity(ConnectionIt->second.Connection, Connection) || + !IsStableIdentity(ConnectionIt->second.World, World)) { + ++ProfileCounters.LivePolicyConnectionIdentityFallbacks; + return nullptr; + } + + AActor* const Owner = Actor->Owner; + const ObjectIdentity OwnerIdentity = TrackObject(Owner); + const bool OwnerTrackable = !Owner || + ResolveObject(OwnerIdentity) == Owner; + if (!OwnerTrackable) { + ++ProfileCounters.LivePolicyUntrackableOwners; + const uint16_t Slot = ResolveImmediateMetricSlot( + Actor, ReplicationState); + ++ProfileCounters.ImmediateClasses[Slot].UntrackableOwnerCalls; + } + + auto& ActorStates = ConnectionIt->second.Actors; + auto ActorIt = ActorStates.find(Actor); + if (ActorIt == ActorStates.end()) { + if (ActorStates.size() >= MaximumScheduledActorsPerConnection) { + ++ProfileCounters.LivePolicyStateCapacityFallbacks; + ++ProfileCounters.SchedulerStateCapacityDrops; + return nullptr; + } + ActorScheduleState NewState{}; + NewState.Actor = ReplicationState->Identity; + NewState.OwnerPointer = Owner; + NewState.OwnerTrackable = OwnerTrackable; + NewState.Owner = OwnerTrackable ? OwnerIdentity : ObjectIdentity{}; + ActorIt = ActorStates.emplace(Actor, NewState).first; + ++CurrentSchedulerActorStates; + ++ProfileCounters.SchedulerStateInsertions; + ProfileCounters.MaximumSchedulerStates = (std::max)( + ProfileCounters.MaximumSchedulerStates, + CurrentSchedulerActorStates); + } + else if (!IdentitiesEqual(ActorIt->second.Actor, ReplicationState->Identity)) { + ActorIt->second = {}; + ActorIt->second.Actor = ReplicationState->Identity; + ActorIt->second.OwnerPointer = Owner; + ActorIt->second.OwnerTrackable = OwnerTrackable; + ActorIt->second.Owner = + OwnerTrackable ? OwnerIdentity : ObjectIdentity{}; + ++ProfileCounters.SchedulerActorIdentityResets; + } + else if (ActorIt->second.OwnerPointer != Owner || + ActorIt->second.OwnerTrackable != OwnerTrackable || + (OwnerTrackable && + !IdentitiesEqual(ActorIt->second.Owner, OwnerIdentity))) { + ActorIt->second = {}; + ActorIt->second.Actor = ReplicationState->Identity; + ActorIt->second.OwnerPointer = Owner; + ActorIt->second.OwnerTrackable = OwnerTrackable; + ActorIt->second.Owner = + OwnerTrackable ? OwnerIdentity : ObjectIdentity{}; + ++ProfileCounters.SchedulerOwnerChanges; + } + return &ActorIt->second; + } + + static FVector ViewerLocation(UNetConnection* Connection) { + AActor* ViewTarget = Connection ? Connection->ViewTarget : nullptr; + if (!ViewTarget && Connection) + ViewTarget = Connection->PlayerController; + if (!ViewTarget) + return {}; + if (ViewTarget->RootComponent) + return ViewTarget->RootComponent->RelativeLocation; + return ViewTarget->ReplicatedMovement.Location; + } + + static bool IsRelevantForConnection( + UWorld* World, + UNetConnection* Connection, + AActor* Actor + ) { + if (!World || !Connection || !Actor) + return false; + if (Actor->bAlwaysRelevant) + return true; + + AActor* RealViewer = Connection->PlayerController + ? static_cast(Connection->PlayerController) + : Connection->OwningActor; + AActor* ViewTarget = Connection->ViewTarget ? Connection->ViewTarget : RealViewer; + + // Login can briefly precede controller/view-target creation. Omitting + // actors in that interval is less safe than one conservative pass. + if (!RealViewer || !ViewTarget) { + ++ProfileCounters.LivePolicyNativeRelevancyFallbacks; + return !Actor->bOnlyRelevantToOwner || + IsOwnedByConnection(World, Connection, Actor); + } + + const ObjectIdentity RealViewerIdentity = TrackObject(RealViewer); + const ObjectIdentity ViewTargetIdentity = TrackObject(ViewTarget); + if (ResolveObject(RealViewerIdentity) != RealViewer || + ResolveObject(ViewTargetIdentity) != ViewTarget) { + ++ProfileCounters.LivePolicyNativeRelevancyFallbacks; + return true; + } + + if (NativeIsNetRelevantFor) { + const FVector SourceLocation = ViewerLocation(Connection); + ++ProfileCounters.LivePolicyNativeRelevancyCalls; + return NativeIsNetRelevantFor( + Actor, RealViewer, ViewTarget, &SourceLocation); + } + + ++ProfileCounters.LivePolicyNativeRelevancyFallbacks; + if (Actor->bOnlyRelevantToOwner || Actor->bNetUseOwnerRelevancy) + return IsOwnedByConnection(World, Connection, Actor); + return true; + } + + static float EffectiveFrequency( + const AActor* Actor, + uint8_t NoDataCalls, + float AdaptiveFloorHz = 0.0f + ) { + float Frequency = Actor ? Actor->NetUpdateFrequency : SchedulerMaximumFrequencyHz; + if (!std::isfinite(Frequency) || Frequency <= 0.0f) + Frequency = SchedulerMaximumFrequencyHz; + Frequency = (std::clamp)( + Frequency, 0.1f, SchedulerMaximumFrequencyHz); + + if (Actor && NoDataCalls >= AdaptiveNoDataThreshold && + std::isfinite(Actor->MinNetUpdateFrequency) && + Actor->MinNetUpdateFrequency > 0.0f) { + const float Minimum = (std::clamp)( + Actor->MinNetUpdateFrequency, + 0.1f, + SchedulerMaximumFrequencyHz); + const float BoundedMinimum = AdaptiveFloorHz > 0.0f + ? (std::max)(Minimum, AdaptiveFloorHz) + : Minimum; + Frequency = (std::min)(Frequency, BoundedMinimum); + } + return Frequency; + } + + static LivePolicyResult EvaluateLivePolicy( + UWorld* World, + UNetConnection* Connection, + AActor* Actor, + ActorReplicationState* ReplicationState, + UActorChannel* ExistingChannel, + bool BypassFrequency + ) { + LivePolicyResult Result{}; + ActorScheduleState* State = ResolveSchedulingState( + World, Connection, Actor, ReplicationState); + if (!State) { + // Identity/capacity uncertainty must never turn into a stale-history + // skip. Replicate conservatively when the live actor itself is valid. + ++ProfileCounters.LivePolicyIdentityFallbacks; + Result.Decision = LivePolicyDecision::Eligible; + return Result; + } + Result.State = State; + State->LastConsideredAtMs = GetTickCount64(); + + if (!Actor->NetDriverName.IsNone() && + Actor->NetDriverName != NetDriver->NetDriverName) { + ++ProfileCounters.LivePolicyDriverMismatches; + Result.Decision = LivePolicyDecision::DriverMismatch; + return Result; + } + if (State->TemporaryRetired) { + Result.Decision = LivePolicyDecision::TemporaryRetired; + return Result; + } + if (State->TearOffRetired) { + Result.Decision = LivePolicyDecision::TearOffRetired; + return Result; + } + if (!ExistingChannel && Actor->NetDormancy != ENetDormancy::DORM_Awake && + Actor->NetDormancy != ENetDormancy::DORM_Never) { + ++ProfileCounters.LivePolicyDormantDeferrals; + Result.Decision = LivePolicyDecision::Dormant; + return Result; + } + + const bool OwnerSensitive = + Actor->bOnlyRelevantToOwner || Actor->bNetUseOwnerRelevancy; + if (OwnerSensitive && Actor->Owner && !State->OwnerTrackable && + !ExistingChannel && !IsDirectConnectionActor(Connection, Actor)) { + ++ProfileCounters.LivePolicyOwnerSensitiveDeferrals; + const uint16_t Slot = ResolveImmediateMetricSlot( + Actor, ReplicationState); + ++ProfileCounters.ImmediateClasses[Slot].OwnerSensitiveDeferrals; + Result.Decision = LivePolicyDecision::NotRelevant; + return Result; + } + if (!ExistingChannel && (Actor->bNetStartup || Actor->bNetLoadOnClient)) + ++ProfileCounters.LivePolicyLevelInitializationUnavailable; + + const TransformSample CurrentSample = SampleActorTransform(Actor); + const bool DormancyChanged = State->LastDormancy != Actor->NetDormancy; + Result.Moved = Actor->bReplicateMovement && + (!State->LastSample.Valid || + VectorDistanceSquared(CurrentSample.Location, State->LastSample.Location) > 0.01f || + RotationDelta(CurrentSample.Rotation, State->LastSample.Rotation) > 0.01f); + if (DormancyChanged || Result.Moved) { + State->ConsecutiveNoDataCalls = 0; + State->NextFrequencyDeadlineMs = 0; + State->NextRelevancyCheckMs = 0; + } + State->LastDormancy = Actor->NetDormancy; + State->LastSample = CurrentSample; + + const ULONGLONG NowMs = State->LastConsideredAtMs; + if (!BypassFrequency && State->RelevancyKnown && !State->LastRelevant && + NowMs < State->NextRelevancyCheckMs) { + ++ProfileCounters.LivePolicyIrrelevantSkips; + const uint16_t Slot = ResolveImmediateMetricSlot( + Actor, ReplicationState); + ++ProfileCounters.ImmediateClasses[Slot].IrrelevantSkips; + Result.Decision = LivePolicyDecision::NotRelevant; + return Result; + } + if (!BypassFrequency && ExistingChannel && + State->NextFrequencyDeadlineMs != 0 && + NowMs < State->NextFrequencyDeadlineMs) { + ++ProfileCounters.LivePolicyNotDue; + Result.Decision = LivePolicyDecision::NotDue; + return Result; + } + if (!BypassFrequency && ExistingChannel) + ++ProfileCounters.LivePolicyDue; + + const bool MustCheckRelevancy = + BypassFrequency || !ExistingChannel || OwnerSensitive || + Actor->bReplicateMovement || !State->RelevancyKnown || + NowMs >= State->NextRelevancyCheckMs; + if (MustCheckRelevancy) { + ++ProfileCounters.LivePolicyRelevancyAudits; + if (State->RelevancyKnown && !State->LastRelevant && + State->LastRelevancyCheckMs && NowMs >= State->LastRelevancyCheckMs) { + ProfileCounters.LivePolicyRelevancyRecheckMilliseconds += + NowMs - State->LastRelevancyCheckMs; + ++ProfileCounters.LivePolicyRelevancyRecheckSamples; + } + const bool Relevant = + OwnerSensitive && Actor->Owner && !State->OwnerTrackable + ? true + : IsRelevantForConnection(World, Connection, Actor); + if (OwnerSensitive && Actor->Owner && !State->OwnerTrackable) + ++ProfileCounters.LivePolicyNativeRelevancyFallbacks; + State->RelevancyKnown = true; + State->LastRelevant = Relevant; + State->LastRelevancyCheckMs = NowMs; + State->NextRelevancyCheckMs = NowMs + + (Relevant && !OwnerSensitive && !Actor->bReplicateMovement + ? StaticRelevancyAuditMs + : IrrelevantRecheckMs); + if (!Relevant) { + ++ProfileCounters.LivePolicyNotRelevant; + Result.Decision = LivePolicyDecision::NotRelevant; + return Result; + } + ++ProfileCounters.LivePolicyRelevant; + } + + ++ProfileCounters.LivePolicyEligible; + Result.Decision = LivePolicyDecision::Eligible; + return Result; + } + + static void FinishLivePolicy( + AActor* Actor, + UActorChannel* Channel, + ActorScheduleState* State, + bool Attempted, + bool ProducedData, + float AdaptiveFloorHz = 0.0f + ) { + if (!Actor || !State || !Attempted) + return; + + const ULONGLONG NowMs = GetTickCount64(); + State->LastAttemptAtMs = NowMs; + if (ProducedData) { + State->LastSuccessfulDataAtMs = NowMs; + State->ConsecutiveNoDataCalls = 0; + if (Actor->bTearOff) { + State->TearOffRetired = true; + ++ProfileCounters.LivePolicyTearOffRetirements; + } + } + else if (State->ConsecutiveNoDataCalls < + (std::numeric_limits::max)()) { + ++State->ConsecutiveNoDataCalls; + } + + if (Actor->bNetTemporary && Channel && + IsChannelOpenAcknowledged(Channel)) { + State->TemporaryRetired = true; + ++ProfileCounters.LivePolicyTemporaryRetirements; + } + + const float Frequency = EffectiveFrequency( + Actor, State->ConsecutiveNoDataCalls, AdaptiveFloorHz); + State->NextFrequencyDeadlineMs = NowMs + static_cast( + std::ceil(1000.0f / Frequency)); + } + + static float ComputeLivePriority( + UNetConnection* Connection, + AActor* Actor, + ActorScheduleState* State, + UActorChannel* ExistingChannel + ) { + float BasePriority = Actor && std::isfinite(Actor->NetPriority) + ? (std::clamp)(Actor->NetPriority, 0.1f, 100.0f) + : 1.0f; + const ULONGLONG NowMs = GetTickCount64(); + const ULONGLONG LastSuccess = State ? State->LastSuccessfulDataAtMs : 0; + const float AgeSeconds = LastSuccess && NowMs > LastSuccess + ? static_cast(NowMs - LastSuccess) / 1000.0f + : 1.0f; + float Priority = BasePriority * (1.0f + (std::min)(AgeSeconds, 4.0f)); + if (!ExistingChannel) Priority += 1000.0f; + if (Actor && Actor->bReplicateMovement) Priority += 100.0f; + if (Actor && (IsDirectConnectionActor(Connection, Actor) || + (State && State->OwnerTrackable && + IsOwnedByConnection(NetDriver->World, Connection, Actor)))) + Priority += 500.0f; + return Priority; + } + + static size_t PriorityBand(float Priority) { + if (!std::isfinite(Priority) || Priority <= 1.0f) return 0; + if (Priority <= 10.0f) return 1; + if (Priority <= 100.0f) return 2; + if (Priority <= 1000.0f) return 3; + return 4; + } + + static bool ReplicateActorForConnection( + UNetConnection* Connection, + AActor* Actor, + bool IsPlayerController, + FName& ChannelName, + CreateChannelByNameFn CreateChannelByName, + SetChannelActorFn SetChannelActor, + ReplicateActorFn ReplicateActor, + UpdateCameraFn UpdateCamera, + ReplicationBucket Bucket, + bool IsBootstrapReplication, + ActorReplicationState* ReplicationState, + bool* ReplicationAttempted = nullptr + ) { + if (ReplicationAttempted) + *ReplicationAttempted = false; + if (!Connection || !Actor || !ReplicationState || + ResolveObject(ReplicationState->Identity) != Actor) { + ConsiderCacheDirtyForNextConnection = true; + return false; + } + + const uint32_t NetworkTick = CurrentNetworkTick(); + if (ReplicationState->LastPreReplicationTick != NetworkTick) { + if (!NativeCallActorPreReplication || !NetDriver) { + ++ProfileCounters.PreReplicationUnavailable; + return false; + } + + NativeCallActorPreReplication(Actor, NetDriver); + ReplicationState->LastPreReplicationTick = NetworkTick; + ++ProfileCounters.PreReplicationCalls; + + // PreReplication is arbitrary native/game code. Match Unreal's + // safety boundary by revalidating the actor before touching a + // channel after the callback returns. + if (ResolveObject(ReplicationState->Identity) != Actor || + Actor->bActorIsBeingDestroyed || !Actor->bReplicates) { + ++ProfileCounters.PreReplicationInvalidatedActors; + ConsiderCacheDirtyForNextConnection = true; + return false; + } + } + + if (IsPlayerController) { + if (Actor != Connection->OwningActor) + return false; + + APlayerController* PlayerController = static_cast(Actor); + Connection->ViewTarget = PlayerController->GetViewTarget(); + UpdateCamera(PlayerController); + } + + UActorChannel* ActorChannel = nullptr; + auto ActorChannelIt = ScratchActorChannels.find(Actor); + + if (ActorChannelIt != ScratchActorChannels.end()) { + ActorChannel = ActorChannelIt->second; + ++ProfileCounters.ActorChannelsReused; + } + else { + ActorChannel = CreateChannelByName(Connection, &ChannelName, OpenChannelFlag, -1); + + if (ActorChannel) { + SetChannelActor(ActorChannel, Actor, 0); + ScratchActorChannels[Actor] = ActorChannel; + ++ProfileCounters.ActorChannelsCreated; + } + } + + if (!ActorChannel || !ActorChannel->Actor) + return false; + + int& ReplicationFlags = *reinterpret_cast((uintptr_t)ActorChannel + OffsetActorChannelReplicationFlags); + + if (!(ReplicationFlags & ReplicationFlagNeedsTick)) + ReplicationFlags |= ReplicationFlagNeedsTick; + + ++ProfileCounters.ChannelStateSamples; + if (IsChannelOpenAcknowledged(ActorChannel)) + ++ProfileCounters.ChannelOpenAcknowledgedSamples; + else + ++ProfileCounters.ChannelNotOpenAcknowledgedSamples; + + ++ProfileCounters.ReplicationAttempts; + ++ProfileCounters.ReplicationAttemptsByBucket[static_cast(Bucket)]; + if (IsBootstrapReplication) + ++ProfileCounters.BootstrapReplicationAttempts; + if (ReplicationAttempted) + *ReplicationAttempted = true; + const bool ProducedData = ReplicateActor(ActorChannel); + RecordClassReplicationResult(Actor, ReplicationState, Bucket, ProducedData); + if (ProducedData) { + ++ProfileCounters.ReplicationSuccesses; + ++ProfileCounters.ReplicationSuccessesByBucket[static_cast(Bucket)]; + if (IsBootstrapReplication) + ++ProfileCounters.BootstrapReplicationSuccesses; + } + + return ProducedData; +} diff --git a/UndauntedInternalServer/Networking/Private/ListenDispatch.inl b/UndauntedInternalServer/Networking/Private/ListenDispatch.inl new file mode 100644 index 0000000..ebdf927 --- /dev/null +++ b/UndauntedInternalServer/Networking/Private/ListenDispatch.inl @@ -0,0 +1,690 @@ + bool Listen(UEngine* Engine, int Port) { + return Listen(Engine, UWorld::GetWorld(), Port); + } + + const std::string& GetLastListenError() { + return LastListenError; + } + + bool Listen(UEngine* Engine, UWorld* World, int Port) { + BaseAddress = (uintptr_t)GetModuleHandleA(nullptr); + LastListenError.clear(); + NativeDispatchOwnershipMismatchLogged = false; + + const auto FailListen = [](const std::string& Reason) { + LastListenError = Reason; + std::cout << "Listen failed: " << Reason << std::endl; + return false; + }; + + if (!Engine || !World) + return FailListen("engine or world missing"); + + constexpr uint8_t IsNetRelevantForSignature[] = { + 0x48, 0x89, 0x5C, 0x24, 0x18, 0x56, 0x57, 0x41, + 0x56, 0x48, 0x83, 0xEC, 0x70, 0x48, 0x8B, 0xF2 + }; + constexpr uint8_t CallPreReplicationSignature[] = { + 0x48, 0x85, 0xD2, 0x0F, 0x84, 0xB1, 0x01, 0x00, + 0x00, 0x53, 0x55, 0x48, 0x83, 0xEC, 0x38 + }; + const uintptr_t IsNetRelevantForAddress = + BaseAddress + OffsetActorIsNetRelevantFor; + MEMORY_BASIC_INFORMATION RelevancyMemory{}; + const bool RelevancyExecutable = + VirtualQuery(reinterpret_cast(IsNetRelevantForAddress), + &RelevancyMemory, sizeof(RelevancyMemory)) == sizeof(RelevancyMemory) && + RelevancyMemory.State == MEM_COMMIT && + (RelevancyMemory.Protect & ( + PAGE_EXECUTE | PAGE_EXECUTE_READ | + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; + NativeRelevancySignatureValid = RelevancyExecutable && + std::memcmp(reinterpret_cast(IsNetRelevantForAddress), + IsNetRelevantForSignature, + sizeof(IsNetRelevantForSignature)) == 0; + NativeIsNetRelevantFor = NativeRelevancySignatureValid + ? reinterpret_cast(IsNetRelevantForAddress) + : nullptr; + std::cout << "Native actor relevancy: " + << (NativeIsNetRelevantFor ? "AActor::IsNetRelevantFor" : + "conservative custom fallback") + << std::endl; + + const uintptr_t CallPreReplicationAddress = + BaseAddress + OffsetActorCallPreReplication; + MEMORY_BASIC_INFORMATION PreReplicationMemory{}; + const bool PreReplicationExecutable = + VirtualQuery(reinterpret_cast(CallPreReplicationAddress), + &PreReplicationMemory, sizeof(PreReplicationMemory)) == + sizeof(PreReplicationMemory) && + PreReplicationMemory.State == MEM_COMMIT && + (PreReplicationMemory.Protect & ( + PAGE_EXECUTE | PAGE_EXECUTE_READ | + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; + NativePreReplicationSignatureValid = PreReplicationExecutable && + std::memcmp(reinterpret_cast(CallPreReplicationAddress), + CallPreReplicationSignature, + sizeof(CallPreReplicationSignature)) == 0; + NativeCallActorPreReplication = NativePreReplicationSignatureValid + ? reinterpret_cast( + CallPreReplicationAddress) + : nullptr; + std::cout << "Native actor pre-replication: " + << (NativeCallActorPreReplication + ? "AActor::CallPreReplication" + : "signature mismatch") + << std::endl; + if (!NativeCallActorPreReplication) + return FailListen( + "AActor::CallPreReplication signature validation failed"); + + FName GameNetDriver = UKismetStringLibrary::Conv_StringToName(L"GameNetDriver"); + NativeGameNetDriverName = GameNetDriver; + NativeOwnershipConflictLogged = false; + CreateNamedNetDriverFn CreateNamedNetDriver = reinterpret_cast(BaseAddress + OffsetCreateNamedNetDriver); + + const bool NetDriverCreated = CreateNamedNetDriver( + Engine, World, GameNetDriver, GameNetDriver) != 0; + std::cout << "Net driver create: " << static_cast(NetDriverCreated) << std::endl; + if (!NetDriverCreated) + return FailListen("CreateNamedNetDriver failed for GameNetDriver"); + + NetDriver = FindNetDriverByName(Engine, World, GameNetDriver); + + if (!NetDriver) + return FailListen("created GameNetDriver is absent from the active world context"); + + const ObjectIdentity NetDriverIdentity = TrackObject(NetDriver); + if (ResolveObject(NetDriverIdentity) != NetDriver || + !NetDriver->IsA(UIpNetDriver::StaticClass())) { + return FailListen("GameNetDriver identity or IpNetDriver class validation failed"); + } + + std::cout << NetDriver->GetFullName() << std::endl; + + ResetConsiderCache(); + + // Match Unreal's native listen ordering. SetReplicationDriver calls + // InitializeActorsInWorld from inside InitListen, so the world's + // authoritative NetDriver pointer must already be visible then. + World->NetDriver = NetDriver; + SetNetDriverWorldFn SetNetDriverWorld = reinterpret_cast(BaseAddress + OffsetSetNetDriverWorld); + SetNetDriverWorld(NetDriver, World); + + // The synthetic dedicated server uses UIpNetDriver for transport and + // the scheduler below for actor selection. The client-cooked Archon + // graph cannot be initialized faithfully here. + NetDriver->ReplicationDriverClass = nullptr; + + FURL url = FURL(); + url.Port = Port; + + FString empty = FString(); + InitListenFn InitListen = *(reinterpret_cast( + *reinterpret_cast(NetDriver) + OffsetNetDriverInitListen)); + // UWorld implements FNetworkNotify as an embedded secondary base at + // World+0x28. InitListen expects the address of that subobject, not the + // vtable value stored in its first field. + void* const ExpectedNetworkNotify = static_cast(&World->NetworkNotify); + bool ListenStatus = InitListen(NetDriver, ExpectedNetworkNotify, + &url, false, &empty); + + std::cout << "Listen Status: " << ListenStatus << std::endl; + + if (!ListenStatus) + return FailListen("UIpNetDriver::InitListen returned false"); + + void* const InstalledNetworkNotify = *reinterpret_cast( + reinterpret_cast(NetDriver) + OffsetNetDriverNotify); + std::cout << "Native network notify validation: installed=" << InstalledNetworkNotify + << " expected=" << ExpectedNetworkNotify + << " world=" << World + << " worldNetDriver=" << World->NetDriver + << " driverWorld=" << NetDriver->World << std::endl; + if (InstalledNetworkNotify != ExpectedNetworkNotify || + World->NetDriver != NetDriver || NetDriver->World != World) { + return FailListen("native NetDriver world/network-notify validation failed"); + } + + if (NetDriver->ReplicationDriver) + return FailListen("unexpected native replication driver with custom scheduler"); + std::cout << "Native network transport: " << NetDriver->GetFullName() + << " (optimized actor scheduler)" << std::endl; + return true; + } + + static bool IsAuthoritativeNativeOwnership(UNetDriver* Driver, UWorld* World, + bool RequireReciprocal) { + if (!Driver || Driver != NetDriver || !World) + return false; + const ObjectIdentity DriverIdentity = TrackObject(Driver); + const ObjectIdentity WorldIdentity = TrackObject(World); + if (ResolveObject(DriverIdentity) != Driver || ResolveObject(WorldIdentity) != World || + !Driver->IsA(UIpNetDriver::StaticClass())) + return false; + UEngine* Engine = UEngine::GetEngine(); + if (!Engine || FindNetDriverByName(Engine, World, NativeGameNetDriverName) != Driver) + return false; + void* const ExpectedNotify = static_cast(&World->NetworkNotify); + void* const InstalledNotify = *reinterpret_cast( + reinterpret_cast(Driver) + OffsetNetDriverNotify); + return Driver->World == World && InstalledNotify == ExpectedNotify && + (!RequireReciprocal || World->NetDriver == Driver); + } + + static bool EnsureNativeReciprocalOwnership(UNetDriver* Driver, const char* Stage) { + UWorld* World = UWorld::GetWorld(); + if (IsAuthoritativeNativeOwnership(Driver, World, true)) + return true; + if (!IsAuthoritativeNativeOwnership(Driver, World, false)) { + ++ProfileCounters.NativeDispatchBlocked; + if (World && World->NetDriver && World->NetDriver != Driver) { + ++ProfileCounters.NativeDispatchConflicts; + if (!NativeOwnershipConflictLogged) { + NativeOwnershipConflictLogged = true; + std::cout << "Native network ownership conflict at " << Stage + << ": expected=" << Driver << " actual=" << World->NetDriver << std::endl; + QueueConnectionEvent({ ConnectionEventType::NativeDispatchConflict, nullptr, + CurrentNetworkTick(), 0, 0, 0 }); + } + } + return false; + } + if (World->NetDriver != nullptr) + return false; + + World->NetDriver = Driver; + if (World->NetDriver != Driver) { + ++ProfileCounters.NativeDispatchBlocked; + return false; + } + ++ProfileCounters.NativeReciprocalRepairs; + if (!NativeDispatchOwnershipMismatchLogged) { + NativeDispatchOwnershipMismatchLogged = true; + std::cout << "Native reciprocal ownership restored at " << Stage + << ": driver=" << Driver << " world=" << World << std::endl; + QueueConnectionEvent({ ConnectionEventType::NativeReciprocalRepair, nullptr, + CurrentNetworkTick(), 0, 0, 0 }); + } + return true; + } + + bool PrepareNativeNetworkDispatch(UNetDriver* Driver) { + if (!Driver || Driver != NetDriver) + return true; + const bool OwnershipValidAtEntry = + IsAuthoritativeNativeOwnership(Driver, UWorld::GetWorld(), true); + if (OwnershipValidAtEntry) + ++ProfileCounters.NativeDispatchOwnershipValidBefore; + const bool Ready = EnsureNativeReciprocalOwnership(Driver, "before TickDispatch"); + return Ready; + } + + void NotifyNativeNetworkDispatchCompleted(UNetDriver* Driver) { + if (!Driver || Driver != NetDriver) + return; + ++ProfileCounters.NativeDispatchCalls; + const bool ValidAfter = + IsAuthoritativeNativeOwnership(Driver, UWorld::GetWorld(), true); + if (ValidAfter) + ++ProfileCounters.NativeDispatchOwnershipValidAfter; + else { + ++ProfileCounters.NativeDispatchOwnershipLostInside; + EnsureNativeReciprocalOwnership(Driver, "after TickDispatch"); + } + } + + bool PrepareNativeNetworkFlush(UNetDriver* Driver) { + if (!Driver || Driver != NetDriver) + return true; + const bool OwnershipValidAtEntry = + IsAuthoritativeNativeOwnership(Driver, UWorld::GetWorld(), true); + if (OwnershipValidAtEntry) + ++ProfileCounters.NativeFlushOwnershipValidBefore; + const bool Ready = EnsureNativeReciprocalOwnership(Driver, "before TickFlush"); + return Ready; + } + + void NotifyNativeNetworkFlushCompleted(UNetDriver* Driver) { + if (!Driver || Driver != NetDriver) + return; + ++ProfileCounters.NativeFlushCalls; + const bool ValidAfter = + IsAuthoritativeNativeOwnership(Driver, UWorld::GetWorld(), true); + if (ValidAfter) + ++ProfileCounters.NativeFlushOwnershipValidAfter; + else + ++ProfileCounters.NativeFlushOwnershipLostInside; + } + + void NotifyNativeEngineTickCompleted(UWorld* World) { + if (!NetDriver || !World) + return; + if (!IsAuthoritativeNativeOwnership(NetDriver, World, true)) + ++ProfileCounters.NativeOwnershipMissingAfterEngineTick; + } + + void NotifyNativePostLogin(APlayerController* PlayerController) { + const ObjectIdentity ControllerIdentity = TrackObject(PlayerController); + if (!PlayerController || ResolveObject(ControllerIdentity) != PlayerController) + return; + + UNetConnection* Connection = + FindConnectionForController(PlayerController); + ++ProfileCounters.NativePostLogins; + QueueConnectionEvent({ ConnectionEventType::NativePostLogin, Connection, + CurrentNetworkTick(), static_cast(ControllerIdentity.Index), + Connection ? 1u : 0u, 0 }); + } + + void TickNetworking() { + TickNetworking(UWorld::GetWorld()); + } + + + void TickNetworking(UWorld* World) { + ProfileCounters.LastCompletedPhase = 1; + if (!World || !NetDriver) { + if (!ConnectionBootstrapStates.empty() || !ActorReplicationStates.empty() || CachedWorld) + ResetConsiderCache(); + CurrentConnections.clear(); + LiveConnections.clear(); + return; + } + + if (CachedWorld && CachedWorld != World) + ResetConsiderCache(); + + World->NetDriver = NetDriver; + NetDriver->World = World; + + static FName ChannelName = UKismetStringLibrary::Conv_StringToName(L"Actor"); + static UClass* ActorChannelClass = UActorChannel::StaticClass(); + static ReplicationClassCache ClassCache = { + APlayerController::StaticClass(), + AArchonCharacter::StaticClass(), + AArchonBehemoth::StaticClass(), + AArchonLantern::StaticClass(), + AArchonEquipment::StaticClass(), + Aprojectile_base_bp_C::StaticClass(), + APawn::StaticClass(), + APlayerState::StaticClass(), + AArchonLoadout::StaticClass(), + AArchonInventory::StaticClass(), + AArchonBuff::StaticClass(), + AArchonAoe::StaticClass(), + AArchonBeam::StaticClass(), + AAbilityActor::StaticClass(), + AMonsterPartActor::StaticClass() + }; + static CreateChannelByNameFn CreateChannelByName = reinterpret_cast(BaseAddress + OffsetCreateChannelByName); + static SetChannelActorFn SetChannelActor = reinterpret_cast(BaseAddress + OffsetSetChannelActor); + static ReplicateActorFn ReplicateActor = reinterpret_cast(BaseAddress + OffsetActorChannelReplicateActor); + static UpdateCameraFn UpdateCamera = reinterpret_cast(BaseAddress + OffsetPlayerControllerUpdateCamera); + + uint32_t& NetworkTick = *reinterpret_cast((uintptr_t)NetDriver + OffsetNetDriverTickCount); + ++NetworkTick; + RefreshConnectionState(NetDriver, NetworkTick); + ProfileCounters.LastCompletedPhase = 2; + ++ProfileCounters.SchedulerTickCalls; + ++ProfileCounters.ConnectionSamples; + ProfileCounters.ConnectionTotal += LiveConnections.size(); + ProfileCounters.MaximumConnections = (std::max)(ProfileCounters.MaximumConnections, + static_cast(LiveConnections.size())); + + // There is no useful actor-channel work without a client. Preserve the + // driver/world bookkeeping above, then force a fresh candidate snapshot + // as soon as the first connection arrives. + if (LiveConnections.empty()) { + ++ProfileCounters.ZeroConnectionTicks; + ConsiderCacheDirtyForNextConnection = true; + EmitPendingConnectionEvents(); + ProfileCounters.BootstrapPhaseConnections.fill(0); + ProfileCounters.LastCompletedPhase = 6; + return; + } + + UrgentCandidateBatch UrgentCandidates{}; + CacheRebuildReason RebuildReason = CacheRebuildReason::Forced; + if (ConsiderCacheDirtyForNextConnection || + GetCacheRebuildReason(World, RebuildReason)) { + RebuildConsiderCache(World, NetDriver, ClassCache, RebuildReason); + ConsiderCacheDirtyForNextConnection = false; + } + const int64_t CandidateSelectionStartedAt = PerformanceTimestamp(); + BuildReplicateList(NetworkTick); + ProfileCounters.CandidateSelectionMicroseconds += + ElapsedMicroseconds(CandidateSelectionStartedAt); + ++ProfileCounters.CandidateSelectionTimingSamples; + UrgentCandidates = BuildUrgentCandidateBatch(GetTickCount64()); + ProfileCounters.LastCompletedPhase = 3; + + for (UNetConnection* Connection : LiveConnections) { + const int64_t ChannelScanStartedAt = PerformanceTimestamp(); + BuildActorChannelScratchMap(Connection, ActorChannelClass); + ProfileCounters.ChannelScanMicroseconds += ElapsedMicroseconds(ChannelScanStartedAt); + ++ProfileCounters.ChannelScanTimingSamples; + + if (Connection->PlayerController) + Connection->ViewTarget = Connection->PlayerController->GetViewTarget(); + + auto StateIt = ConnectionBootstrapStates.find(Connection); + ConnectionBootstrapState* Bootstrap = StateIt != ConnectionBootstrapStates.end() + ? &StateIt->second + : nullptr; + const ULONGLONG NowMs = GetTickCount64(); + if (Bootstrap) + RefreshPawnState(Connection, *Bootstrap, NetworkTick, NowMs); + const bool IsBootstrapping = Bootstrap && + Bootstrap->Phase == ConnectionBootstrapPhase::Bootstrapping; + if (++CurrentConnectionPass == 0) + ++CurrentConnectionPass; + const uint64_t ConnectionPass = CurrentConnectionPass; + + if (IsBootstrapping) { + const int64_t BootstrapSelectionStartedAt = PerformanceTimestamp(); + DiscoverCriticalActors(World, Connection, *Bootstrap, NetworkTick, NowMs); + ProfileCounters.CandidateSelectionMicroseconds += + ElapsedMicroseconds(BootstrapSelectionStartedAt); + ++ProfileCounters.CandidateSelectionTimingSamples; + } + + const int64_t ReplicationStartedAt = PerformanceTimestamp(); + const auto TryCandidate = [&](ReplicationCandidate& Candidate, + bool BypassFrequency, bool BootstrapReplication, + bool MovementPrepass, float AdaptiveFloorHz = 0.0f, + bool* OutAttempted = nullptr) { + if (OutAttempted) + *OutAttempted = false; + if (Candidate.LastReplicatedConnectionPass == ConnectionPass) { + ++ProfileCounters.LivePolicyDuplicateSkips; + return false; + } + + AActor* Actor = Candidate.Actor; + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Actor || + !IsActorReplicationCandidate(World, Actor)) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + return false; + } + + UActorChannel* ExistingChannel = nullptr; + const auto ExistingIt = ScratchActorChannels.find(Actor); + if (ExistingIt != ScratchActorChannels.end()) + ExistingChannel = ExistingIt->second; + + const LivePolicyResult Policy = EvaluateLivePolicy( + World, Connection, Actor, Candidate.ReplicationState, + ExistingChannel, BypassFrequency); + Candidate.LastReplicatedConnectionPass = ConnectionPass; + if (Policy.Decision != LivePolicyDecision::Eligible) { + if (BypassFrequency) + ++ProfileCounters.LivePolicyCriticalRejected; + return false; + } + + bool ReplicationAttempted = false; + const bool ProducedData = ReplicateActorForConnection( + Connection, Actor, Candidate.IsPlayerController, + ChannelName, CreateChannelByName, SetChannelActor, + ReplicateActor, UpdateCamera, Candidate.Bucket, + BootstrapReplication, Candidate.ReplicationState, + &ReplicationAttempted); + UActorChannel* ResultingChannel = nullptr; + const auto ResultingIt = ScratchActorChannels.find(Actor); + if (ResultingIt != ScratchActorChannels.end()) + ResultingChannel = ResultingIt->second; + FinishLivePolicy(Actor, ResultingChannel, Policy.State, + ReplicationAttempted, ProducedData, AdaptiveFloorHz); + + if (MovementPrepass && ReplicationAttempted) { + ++ProfileCounters.LivePolicyMovementPrepassAttempts; + if (ProducedData) + ++ProfileCounters.LivePolicyMovementPrepassSuccesses; + } + if (OutAttempted) + *OutAttempted = ReplicationAttempted; + return ProducedData; + }; + + for (size_t UrgentIndex = 0; UrgentIndex < UrgentCandidates.Count; ++UrgentIndex) { + UrgentCandidateEntry& Urgent = UrgentCandidates.Entries[UrgentIndex]; + ReplicationCandidate* Candidate = Urgent.Candidate; + if (!Candidate || (Urgent.TargetConnection.Object && + ResolveObject(Urgent.TargetConnection) != Connection)) { + continue; + } + // Multiple outcomes can promote the same actor in one engine pass. + // Preserve each reason's queued metrics without replicating the actor twice. + if (Candidate->LastReplicatedConnectionPass == ConnectionPass) + continue; + + bool ReplicationAttempted = false; + const bool ProducedData = TryCandidate(*Candidate, true, false, + false, 0.0f, &ReplicationAttempted); + const size_t ReasonIndex = UrgentReasonIndex(Urgent.Reason); + if (ProducedData) { + ++ProfileCounters.UrgentDamageReplicationSuccesses; + ++ProfileCounters.UrgentSuccessesByReason[ReasonIndex]; + } + if (ReplicationAttempted) { + Candidate->LastReplicatedConnectionPass = ConnectionPass; + ++ProfileCounters.UrgentDamageReplicationAttempts; + ++ProfileCounters.UrgentAttemptsByReason[ReasonIndex]; + ++ProfileCounters.UrgentDamageLatencySamples; + ProfileCounters.UrgentDamageLatencyMilliseconds += + NowMs - Urgent.QueuedAtMs; + ++ProfileCounters.UrgentLatencySamplesByReason[ReasonIndex]; + ProfileCounters.UrgentLatencyMillisecondsByReason[ReasonIndex] += + NowMs - Urgent.QueuedAtMs; + } + else { + RetryUrgentCandidateForConnection(Urgent, Connection); + } + } + + // Critical and connection-owned actors are considered first on every + // 30 Hz pass. Native relevancy still prevents owner-only leakage. + for (ReplicationCandidate& Candidate : ConsiderCache) { + AActor* Actor = Candidate.Actor; + if (Candidate.LastReplicatedConnectionPass == ConnectionPass || + IsUrgentCandidate(UrgentCandidates, &Candidate, Connection)) { + continue; + } + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Actor) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + continue; + } + if (!IsActorReplicationCandidate(World, Actor)) + continue; + + InitializationActorRole Role{}; + uint8_t OwnerDepth = 0; + const bool ConnectionCritical = + FindInitializationRole(World, Connection, Actor, Role, OwnerDepth); + if (!ConnectionCritical && !Candidate.IsCritical) + continue; + + ++ProfileCounters.CriticalPrepassCandidates; + bool ReplicationAttempted = false; + const bool CoreConnectionActor = + ConnectionCritical && + Role != InitializationActorRole::OwnedActor; + const bool LatencySensitiveOwned = + ConnectionCritical && + IsLatencySensitiveOwnedActor(Candidate.ClassFlags); + const bool ForceEveryPass = + IsBootstrapping || Candidate.IsCritical || + CoreConnectionActor || LatencySensitiveOwned; + const float AdaptiveFloorHz = + ConnectionCritical && !ForceEveryPass + ? OwnedCriticalAdaptiveFloorHz + : 0.0f; + if (AdaptiveFloorHz > 0.0f) + ++ProfileCounters.CriticalOwnedFrequencyLimited; + const bool ProducedData = TryCandidate( + Candidate, ForceEveryPass, + IsBootstrapping && ConnectionCritical, false, + AdaptiveFloorHz, &ReplicationAttempted); + if (ReplicationAttempted) { + ++ProfileCounters.CriticalPrepassAttempts; + if (ProducedData) + ++ProfileCounters.CriticalPrepassSuccesses; + } + else { + ++ProfileCounters.CriticalPrepassSetupFallbacks; + } + } + + // Movement-sensitive gameplay actors are next. Their live + // NetUpdateFrequency remains authoritative; observed movement + // merely cancels an adaptive idle deadline. + for (ReplicationCandidate& Candidate : ConsiderCache) { + AActor* Actor = Candidate.Actor; + if (!Actor || Candidate.LastReplicatedConnectionPass == ConnectionPass) + continue; + if (!Actor->bReplicateMovement && + Candidate.Bucket == ReplicationBucket::Immediate) + continue; + TryCandidate(Candidate, false, false, true); + } + + PriorityScratch.clear(); + if (PriorityScratch.capacity() < ConsiderCache.size()) + PriorityScratch.reserve(ConsiderCache.size()); + for (ReplicationCandidate& Candidate : ConsiderCache) { + AActor* Actor = Candidate.Actor; + if (!Actor || Candidate.LastReplicatedConnectionPass == ConnectionPass) + continue; + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Actor || + !IsActorReplicationCandidate(World, Actor)) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + continue; + } + UActorChannel* ExistingChannel = nullptr; + const auto ExistingIt = ScratchActorChannels.find(Actor); + if (ExistingIt != ScratchActorChannels.end()) + ExistingChannel = ExistingIt->second; + const LivePolicyResult Policy = EvaluateLivePolicy( + World, Connection, Actor, Candidate.ReplicationState, + ExistingChannel, false); + Candidate.LastReplicatedConnectionPass = ConnectionPass; + if (Policy.Decision != LivePolicyDecision::Eligible) + continue; + const float Priority = ComputeLivePriority( + Connection, Actor, Policy.State, ExistingChannel); + ++ProfileCounters.LivePolicyPriorityBands[ + PriorityBand(Priority)]; + PriorityScratch.push_back({ + &Candidate, Priority, Policy.State + }); + } + std::sort(PriorityScratch.begin(), PriorityScratch.end(), + [](const PrioritizedCandidate& Left, + const PrioritizedCandidate& Right) { + if (Left.Priority != Right.Priority) + return Left.Priority > Right.Priority; + const int32_t LeftIndex = + Left.Candidate && Left.Candidate->ReplicationState + ? Left.Candidate->ReplicationState->Identity.Index : -1; + const int32_t RightIndex = + Right.Candidate && Right.Candidate->ReplicationState + ? Right.Candidate->ReplicationState->Identity.Index : -1; + return LeftIndex < RightIndex; + }); + ++ProfileCounters.LivePolicyPrioritySorts; + ProfileCounters.LivePolicyPriorityCandidates += PriorityScratch.size(); + for (const PrioritizedCandidate& Prioritized : PriorityScratch) { + if (!Prioritized.Candidate) + continue; + ReplicationCandidate& Candidate = *Prioritized.Candidate; + bool ReplicationAttempted = false; + const bool ProducedData = ReplicateActorForConnection( + Connection, Candidate.Actor, Candidate.IsPlayerController, + ChannelName, CreateChannelByName, SetChannelActor, + ReplicateActor, UpdateCamera, Candidate.Bucket, false, + Candidate.ReplicationState, &ReplicationAttempted); + UActorChannel* ResultingChannel = nullptr; + const auto ResultingIt = + ScratchActorChannels.find(Candidate.Actor); + if (ResultingIt != ScratchActorChannels.end()) + ResultingChannel = ResultingIt->second; + FinishLivePolicy(Candidate.Actor, ResultingChannel, + Prioritized.State, ReplicationAttempted, ProducedData); + } + ProfileCounters.ReplicationMicroseconds += ElapsedMicroseconds(ReplicationStartedAt); + ++ProfileCounters.ReplicationTimingSamples; + + if (IsBootstrapping) { + RefreshCriticalChannelAcknowledgements(Connection, *Bootstrap, NetworkTick, NowMs); + TryCompleteBootstrap(Connection, *Bootstrap, NetworkTick, NowMs); + } + ProfileCounters.LastCompletedPhase = 4; + } + FinalizeImmediatePassMetrics(NetworkTick); + EmitPendingConnectionEvents(); + ProfileCounters.BootstrapPhaseConnections.fill(0); + for (const auto& [Connection, Bootstrap] : ConnectionBootstrapStates) { + const size_t PhaseIndex = static_cast(Bootstrap.Phase); + if (PhaseIndex < ProfileCounters.BootstrapPhaseConnections.size()) + ++ProfileCounters.BootstrapPhaseConnections[PhaseIndex]; + } + ProfileCounters.LastCompletedPhase = 6; + } + + const std::vector& GetLiveConnections() { + return LiveConnections; + } + + ProfilingCounters TakeProfilingCounters() { + ProfileCounters.CurrentSchedulerStates = CurrentSchedulerActorStates; + ProfileCounters.MaximumSchedulerStates = (std::max)( + ProfileCounters.MaximumSchedulerStates, CurrentSchedulerActorStates); + ProfilingCounters Snapshot = ProfileCounters; + PopulateImmediateClassNames(Snapshot); + ProfileCounters = {}; + ProfileCounters.LastCompletedPhase = Snapshot.LastCompletedPhase; + ProfileCounters.CurrentCandidates = static_cast(ConsiderCache.size()); + ProfileCounters.CurrentSchedulerStates = CurrentSchedulerActorStates; + ProfileCounters.MaximumSchedulerStates = CurrentSchedulerActorStates; + ProfileCounters.BootstrapPhaseConnections = Snapshot.BootstrapPhaseConnections; + ResetImmediateClassRegistry(); + ActorPropertyDistributionDirty = true; + return Snapshot; + } + + void ResetWorldState(UWorld* World) { + (void)World; + ResetConsiderCache(); + CachedWorld = nullptr; + CachedWorldIdentity = {}; + NativeDispatchOwnershipMismatchLogged = false; + NativeOwnershipConflictLogged = false; + } + + void ResetDriverState(UNetDriver* Driver) { + if (Driver == NetDriver) + NetDriver = nullptr; + ResetConsiderCache(); + NativeDispatchOwnershipMismatchLogged = false; + NativeOwnershipConflictLogged = false; + } + + void BeginFrameNetworkLifecycle(UWorld* World) { + if (CachedWorld != nullptr && CachedWorld != World) + ResetWorldState(World); + if (NetDriver != nullptr && NetDriver->World != World) + ResetDriverState(NetDriver); + } + + void EndFrameNetworkLifecycle(UWorld* World) { + if (World == nullptr) + ResetWorldState(nullptr); + } +} diff --git a/UndauntedInternalServer/Networking/Private/ReplicationScheduler.inl b/UndauntedInternalServer/Networking/Private/ReplicationScheduler.inl new file mode 100644 index 0000000..1aad46b --- /dev/null +++ b/UndauntedInternalServer/Networking/Private/ReplicationScheduler.inl @@ -0,0 +1,716 @@ + static void BuildActorChannelScratchMap(UNetConnection* Connection, UClass* ActorChannelClass) { + ScratchActorChannels.clear(); + ScratchActorChannels.reserve(Connection ? Connection->OpenChannels.Num() : 0); + + if (!Connection) + return; + + for (UChannel* Channel : Connection->OpenChannels) { + if (!Channel || Channel->Class != ActorChannelClass) + continue; + + UActorChannel* ActorChannel = static_cast(Channel); + + // An actor channel owns the authoritative association here. A newly + // created replicated actor can be valid before a global object-index + // identity probe is stable; filtering it out permits a duplicate + // channel and breaks the initial actor/subobject bunch sequence. + if (ActorChannel->Actor) + ScratchActorChannels[ActorChannel->Actor] = ActorChannel; + } + } + + static uint32_t CurrentNetworkTick() { + return NetDriver + ? *reinterpret_cast((uintptr_t)NetDriver + OffsetNetDriverTickCount) + : 0; + } + + static bool FindInitializationRole( + UWorld* World, + UNetConnection* Connection, + AActor* Actor, + InitializationActorRole& Role, + uint8_t& OwnerDepth + ) { + if (!World || !Connection || !Actor || !Connection->PlayerController) + return false; + + APlayerController* Controller = Connection->PlayerController; + APlayerState* PlayerState = Controller->PlayerState; + APawn* Pawn = Controller->Pawn; + AActor* ViewTarget = Connection->ViewTarget; + + if (Actor == Controller) { + Role = InitializationActorRole::Controller; + OwnerDepth = 0; + return true; + } + if (Actor == World->GameState) { + Role = InitializationActorRole::GameState; + OwnerDepth = 0; + return true; + } + if (Actor == PlayerState) { + Role = InitializationActorRole::PlayerState; + OwnerDepth = 0; + return true; + } + if (Actor == Pawn) { + Role = InitializationActorRole::Pawn; + OwnerDepth = 0; + return true; + } + if (Actor == ViewTarget) { + Role = InitializationActorRole::ViewTarget; + OwnerDepth = 0; + return true; + } + + AActor* Owner = Actor->Owner; + for (size_t Depth = 1; Owner && Depth <= MaximumOwnerChainDepth; ++Depth) { + if (Owner == Controller || Owner == PlayerState || Owner == Pawn) { + Role = InitializationActorRole::OwnedActor; + OwnerDepth = static_cast(Depth); + return true; + } + + const ObjectIdentity OwnerIdentity = TrackObject(Owner); + if (ResolveObject(OwnerIdentity) != Owner) + break; + + if (Owner->bActorIsBeingDestroyed || GetActorWorld(Owner) != World) + break; + + Owner = Owner->Owner; + } + + return false; + } + + static CriticalActorState* EnsureCriticalActor( + UNetConnection* Connection, + ConnectionBootstrapState& State, + AActor* Actor, + InitializationActorRole Role, + uint8_t OwnerDepth, + uint32_t NetworkTick, + ULONGLONG NowMs + ) { + if (!Actor) + return nullptr; + + auto Existing = State.CriticalActors.find(Actor); + if (Existing != State.CriticalActors.end()) + return &Existing->second; + + ObjectIdentity Identity = TrackObject(Actor); + if (!Identity.Object) + return nullptr; + + auto [Inserted, WasInserted] = State.CriticalActors.emplace(Actor, + CriticalActorState{ Identity, Role, OwnerDepth, false }); + if (WasInserted) { + State.LastCriticalActorDiscoveredAtMs = NowMs; + ++ProfileCounters.CriticalActorsDiscovered; + QueueConnectionEvent({ ConnectionEventType::CriticalActorDiscovered, Connection, NetworkTick, + static_cast(Role), OwnerDepth, NowMs - State.StartedAtMs }); + } + return &Inserted->second; + } + + static void PruneCriticalActors(ConnectionBootstrapState& State) { + for (auto It = State.CriticalActors.begin(); It != State.CriticalActors.end();) { + if (!ResolveObject(It->second.Identity)) + It = State.CriticalActors.erase(It); + else + ++It; + } + } + + static void RestartBootstrap( + UNetConnection* Connection, + ConnectionBootstrapState& State, + UWorld* World, + uint32_t NetworkTick, + uint32_t Reason, + ULONGLONG NowMs + ) { + State = MakeConnectionBootstrapState(Connection, World); + State.StartedAtMs = NowMs; + State.LastCriticalActorDiscoveredAtMs = NowMs; + ++ProfileCounters.BootstrapRestarts; + QueueConnectionEvent({ ConnectionEventType::BootstrapRestarted, Connection, NetworkTick, + Reason, 0, 0 }); + } + + static void RefreshPawnState( + UNetConnection* Connection, + ConnectionBootstrapState& State, + uint32_t NetworkTick, + ULONGLONG NowMs + ) { + APawn* CurrentPawn = Connection && Connection->PlayerController + ? Connection->PlayerController->Pawn + : nullptr; + + if (IdentityMatches(State.Pawn, CurrentPawn)) + return; + + RestartBootstrap(Connection, State, NetDriver ? NetDriver->World : nullptr, + NetworkTick, 2u, NowMs); + } + + static void DiscoverCriticalActors( + UWorld* World, + UNetConnection* Connection, + ConnectionBootstrapState& State, + uint32_t NetworkTick, + ULONGLONG NowMs + ) { + if (State.Phase != ConnectionBootstrapPhase::Bootstrapping) + return; + + PruneCriticalActors(State); + + for (ReplicationCandidate& Candidate : ConsiderCache) { + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Candidate.Actor) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + continue; + } + + if (!IsCachedActorStillReplicatable(Candidate.Actor)) + continue; + + InitializationActorRole Role{}; + uint8_t OwnerDepth = 0; + if (!FindInitializationRole(World, Connection, Candidate.Actor, Role, OwnerDepth)) + continue; + + EnsureCriticalActor(Connection, State, Candidate.Actor, Role, OwnerDepth, + NetworkTick, NowMs); + } + } + + static UrgentCandidateBatch BuildUrgentCandidateBatch(ULONGLONG NowMs) { + UrgentCandidateBatch Batch{}; + size_t RetainedCount = 0; + + for (size_t Index = 0; Index < PendingUrgentDamageTargetCount; ++Index) { + const UrgentDamageTarget& Pending = PendingUrgentDamageTargets[Index]; + UObject* Resolved = ResolveObject(Pending.Identity); + if (!Resolved) { + ++ProfileCounters.UrgentDamageTargetsInvalidated; + ++ProfileCounters.UrgentInvalidatedByReason[UrgentReasonIndex(Pending.Reason)]; + continue; + } + if (Pending.TargetConnection.Object && !ResolveObject(Pending.TargetConnection)) { + ++ProfileCounters.UrgentDamageTargetsInvalidated; + ++ProfileCounters.UrgentInvalidatedByReason[UrgentReasonIndex(Pending.Reason)]; + continue; + } + if (NowMs - Pending.QueuedAtMs > UrgentDamageTargetMaximumAgeMs) { + ++ProfileCounters.UrgentDamageTargetsExpired; + ++ProfileCounters.UrgentExpiredByReason[UrgentReasonIndex(Pending.Reason)]; + continue; + } + + ReplicationCandidate* Found = nullptr; + const auto CandidateIt = CandidateActorLookup.find(static_cast(Resolved)); + if (CandidateIt != CandidateActorLookup.end()) { + ReplicationCandidate& Candidate = *CandidateIt->second; + if (!Candidate.ReplicationState || + ResolveObject(Candidate.ReplicationState->Identity) != Candidate.Actor) { + ConsiderCacheDirtyForNextConnection = true; + ++ProfileCounters.SchedulerActorIdentityResets; + } else { + Found = &Candidate; + } + } + + if (!Found) { + ConsiderCacheDirtyForNextConnection = true; + PendingUrgentDamageTargets[RetainedCount++] = Pending; + continue; + } + + // Damage processing can destroy or retire the target before this + // next networking pass. Urgency changes scheduling priority only; + // it must never bypass the ordinary replication eligibility gate. + UWorld* ExpectedWorld = NetDriver ? NetDriver->World : nullptr; + if (!IsActorReplicationCandidate(ExpectedWorld, Found->Actor)) { + ++ProfileCounters.UrgentDamageTargetsInvalidated; + ++ProfileCounters.UrgentInvalidatedByReason[UrgentReasonIndex(Pending.Reason)]; + continue; + } + + if (Batch.Count < Batch.Entries.size()) { + Batch.Entries[Batch.Count++] = { + Found, + Pending.Identity, + Pending.TargetConnection, + Pending.QueuedAtMs, + Pending.SetupRetries, + Pending.Reason + }; + } + else { + ++ProfileCounters.UrgentDamageTargetsDropped; + ++ProfileCounters.UrgentDroppedByReason[UrgentReasonIndex(Pending.Reason)]; + } + } + + PendingUrgentDamageTargetCount = RetainedCount; + return Batch; + } + + static void RetryUrgentCandidateForConnection( + const UrgentCandidateEntry& Entry, + UNetConnection* Connection + ) { + if (Entry.SetupRetries >= 1) { + ++ProfileCounters.UrgentDamageSetupRetryExhausted; + ++ProfileCounters.UrgentDamageTargetsDropped; + ++ProfileCounters.UrgentDroppedByReason[UrgentReasonIndex(Entry.Reason)]; + return; + } + + const ObjectIdentity ConnectionIdentity = TrackObject(Connection); + if (!IsStableIdentity(ConnectionIdentity, Connection)) { + ++ProfileCounters.UrgentDamageTargetsInvalidated; + ++ProfileCounters.UrgentInvalidatedByReason[UrgentReasonIndex(Entry.Reason)]; + return; + } + + for (size_t PendingIndex = 0; + PendingIndex < PendingUrgentDamageTargetCount; ++PendingIndex) { + const UrgentDamageTarget& Pending = PendingUrgentDamageTargets[PendingIndex]; + if (IdentitiesEqual(Pending.Identity, Entry.Identity) && + (!Pending.TargetConnection.Object || + IdentitiesEqual(Pending.TargetConnection, ConnectionIdentity))) { + return; + } + } + if (PendingUrgentDamageTargetCount >= PendingUrgentDamageTargets.size()) { + ++ProfileCounters.UrgentDamageTargetsDropped; + ++ProfileCounters.UrgentDroppedByReason[UrgentReasonIndex(Entry.Reason)]; + return; + } + + PendingUrgentDamageTargets[PendingUrgentDamageTargetCount++] = { + Entry.Identity, + ConnectionIdentity, + Entry.QueuedAtMs, + static_cast(Entry.SetupRetries + 1), + Entry.Reason + }; + ++ProfileCounters.UrgentDamageSetupRetries; + } + + static bool IsUrgentCandidate( + const UrgentCandidateBatch& Batch, + const ReplicationCandidate* Candidate, + UNetConnection* Connection + ) { + for (size_t Index = 0; Index < Batch.Count; ++Index) { + const UrgentCandidateEntry& Entry = Batch.Entries[Index]; + if (Entry.Candidate == Candidate && + (!Entry.TargetConnection.Object || + ResolveObject(Entry.TargetConnection) == Connection)) { + return true; + } + } + return false; + } + + static bool IsChannelOpenAcknowledged(UActorChannel* Channel) { + if (!Channel) + return false; + + const uint32_t Flags = *reinterpret_cast( + reinterpret_cast(Channel) + OffsetChannelFlags); + return (Flags & ChannelOpenAcknowledgedMask) == ChannelOpenAcknowledgedMask; + } + + static void RefreshCriticalChannelAcknowledgements( + UNetConnection* Connection, + ConnectionBootstrapState& State, + uint32_t NetworkTick, + ULONGLONG NowMs + ) { + if (State.Phase != ConnectionBootstrapPhase::Bootstrapping) + return; + + PruneCriticalActors(State); + for (auto& [Actor, Critical] : State.CriticalActors) { + if (!ResolveObject(Critical.Identity)) + continue; + + const auto ChannelIt = ScratchActorChannels.find(Actor); + const bool IsAcknowledged = ChannelIt != ScratchActorChannels.end() && + IsChannelOpenAcknowledged(ChannelIt->second); + if (IsAcknowledged && !Critical.ChannelAcknowledged) { + ++ProfileCounters.CriticalChannelsAcknowledged; + QueueConnectionEvent({ ConnectionEventType::CriticalChannelAcknowledged, + Connection, NetworkTick, static_cast(Critical.Role), + Critical.OwnerDepth, NowMs - State.StartedAtMs }); + } + if (IsAcknowledged) + Critical.ChannelAcknowledged = true; + } + } + + static void TryCompleteBootstrap( + UNetConnection* Connection, + ConnectionBootstrapState& State, + uint32_t NetworkTick, + ULONGLONG NowMs + ) { + if (State.Phase != ConnectionBootstrapPhase::Bootstrapping) + return; + + const ULONGLONG ElapsedMs = NowMs - State.StartedAtMs; + if (ElapsedMs >= BootstrapMaximumDurationMs) { + State.Phase = ConnectionBootstrapPhase::Active; + ++ProfileCounters.BootstrapDeadlines; + QueueConnectionEvent({ ConnectionEventType::BootstrapDeadline, Connection, NetworkTick, + static_cast(State.CriticalActors.size()), + State.PossessionAcknowledged ? 1u : 0u, ElapsedMs }); + return; + } + + bool HasController = false; + bool HasPlayerState = false; + bool HasPawn = false; + bool AllCriticalChannelsAcknowledged = true; + for (const auto& [Actor, Critical] : State.CriticalActors) { + if (!ResolveObject(Critical.Identity)) + continue; + HasController |= Critical.Role == InitializationActorRole::Controller; + HasPlayerState |= Critical.Role == InitializationActorRole::PlayerState; + HasPawn |= Critical.Role == InitializationActorRole::Pawn; + AllCriticalChannelsAcknowledged &= Critical.ChannelAcknowledged; + } + + const bool MinimumDurationElapsed = ElapsedMs >= BootstrapMinimumDurationMs; + const bool Stable = NowMs - State.LastCriticalActorDiscoveredAtMs >= BootstrapStableMs; + if (!MinimumDurationElapsed || !HasController || !HasPlayerState || !HasPawn || + !AllCriticalChannelsAcknowledged || !State.PossessionAcknowledged || !Stable) + return; + + State.Phase = ConnectionBootstrapPhase::Active; + ++ProfileCounters.BootstrapCompleted; + ++ProfileCounters.BootstrapLatencySamples; + ProfileCounters.BootstrapLatencyMilliseconds += ElapsedMs; + QueueConnectionEvent({ ConnectionEventType::BootstrapCompleted, Connection, NetworkTick, + static_cast(State.CriticalActors.size()), 0, ElapsedMs }); + } + + static UNetConnection* FindConnectionForController(APlayerController* PlayerController) { + if (!PlayerController || !NetDriver) + return nullptr; + + for (UNetConnection* Connection : NetDriver->ClientConnections) { + if (!IsOpenConnection(Connection) || Connection->PlayerController != PlayerController) + continue; + + auto StateIt = ConnectionBootstrapStates.find(Connection); + if (StateIt == ConnectionBootstrapStates.end()) { + StateIt = ConnectionBootstrapStates.emplace(Connection, + MakeConnectionBootstrapState(Connection, NetDriver->World)).first; + ++ProfileCounters.BootstrapStarts; + QueueConnectionEvent({ ConnectionEventType::Open, Connection, CurrentNetworkTick(), + static_cast(NetDriver->ClientConnections.Num()), 0, 0 }); + QueueConnectionEvent({ ConnectionEventType::BootstrapStarted, Connection, + CurrentNetworkTick(), 0, 0, 0 }); + } + else if (!IdentityMatches(StateIt->second.Connection, Connection) || + !IdentityMatches(StateIt->second.World, NetDriver->World) || + !IdentityMatches(StateIt->second.Controller, PlayerController)) { + RestartBootstrap(Connection, StateIt->second, NetDriver->World, + CurrentNetworkTick(), 1u, GetTickCount64()); + } + return Connection; + } + return nullptr; + } + + static ConnectionBootstrapState* FindStateForConnection( + UNetConnection* Connection, + APlayerController* PlayerController + ) { + if (!Connection || !PlayerController) + return nullptr; + + auto StateIt = ConnectionBootstrapStates.find(Connection); + if (StateIt == ConnectionBootstrapStates.end() || + ResolveObject(StateIt->second.Connection) != Connection || + ResolveObject(StateIt->second.Controller) != PlayerController) { + return nullptr; + } + return &StateIt->second; + } + + static FName ExpectedWorldPackageName() { + if (NetDriver && NetDriver->WorldPackage && ResolveObject(TrackObject(NetDriver->WorldPackage))) + return NetDriver->WorldPackage->Name; + + UObject* Outer = NetDriver ? NetDriver->World : nullptr; + while (Outer && Outer->Outer) + Outer = Outer->Outer; + return Outer ? Outer->Name : FName{}; + } + + void Configure(uint32_t CacheMaxAgeMilliseconds) { + ConsiderCacheMaxAgeMs = (std::clamp)(static_cast(CacheMaxAgeMilliseconds), 50ull, 5000ull); + } + + void NotifyDamageRpcObserved() { + ++ProfileCounters.UrgentDamageRpcMatches; + } + + void NotifyUrgentDamageTarget(int32_t ObjectIndex, int32_t ObjectSerialNumber) { + ++ProfileCounters.UrgentDamageInterruptCorrelations; + if (ObjectIndex < 0 || ObjectIndex >= UObject::GObjects->Num() || + ObjectSerialNumber == 0) { + ++ProfileCounters.UrgentDamageWeakHandlesInvalid; + return; + } + + FUObjectItem* Item = UObject::GObjects->GetItemByIndex(ObjectIndex); + if (!Item || !Item->Object || Item->SerialNumber != ObjectSerialNumber || + (static_cast(Item->Flags) & 0x30000000u) != 0) { + ++ProfileCounters.UrgentDamageWeakHandlesInvalid; + return; + } + + UObject* RawObject = Item->Object; + if ((RawObject->Flags & EObjectFlags::BeginDestroyed) || + (RawObject->Flags & EObjectFlags::FinishDestroyed) || + !RawObject->IsA(AActor::StaticClass())) { + ++ProfileCounters.UrgentDamageWeakHandlesInvalid; + return; + } + + AActor* Current = static_cast(RawObject); + UWorld* ExpectedWorld = NetDriver ? NetDriver->World : nullptr; + std::array Visited{}; + ObjectIdentity BehemothIdentity{}; + uint32_t BehemothDepth = 0; + + for (size_t Depth = 0; Current && Depth <= MaximumOwnerChainDepth; ++Depth) { + bool Cycle = false; + for (size_t VisitedIndex = 0; VisitedIndex < Depth; ++VisitedIndex) { + if (Visited[VisitedIndex] == Current) { + Cycle = true; + break; + } + } + if (Cycle) + break; + Visited[Depth] = Current; + + const ObjectIdentity CurrentIdentity = TrackObject(Current); + if (!IsStableIdentity(CurrentIdentity, Current) || + (ExpectedWorld && GetActorWorld(Current) != ExpectedWorld)) { + ++ProfileCounters.UrgentDamageWeakHandlesInvalid; + return; + } + if (Current->IsA(AArchonBehemoth::StaticClass())) { + BehemothIdentity = CurrentIdentity; + BehemothDepth = static_cast(Depth); + break; + } + + AActor* Owner = Current->Owner; + if (!Owner) + break; + const ObjectIdentity OwnerIdentity = TrackObject(Owner); + if (!IsStableIdentity(OwnerIdentity, Owner)) { + ++ProfileCounters.UrgentDamageWeakHandlesInvalid; + return; + } + Current = Owner; + } + + if (!BehemothIdentity.Object) { + ++ProfileCounters.UrgentDamageTargetsWithoutBehemoth; + return; + } + if (BehemothDepth == 0) + ++ProfileCounters.UrgentDamageDirectBehemothTargets; + else + ++ProfileCounters.UrgentDamageOwnerChainTargets; + + for (size_t Index = 0; Index < PendingUrgentDamageTargetCount; ++Index) { + const UrgentDamageTarget& Pending = PendingUrgentDamageTargets[Index]; + if (Pending.Reason == UrgentReplicationReason::Stagger && + IdentitiesEqual(Pending.Identity, BehemothIdentity) && + !Pending.TargetConnection.Object) { + ++ProfileCounters.UrgentDamageTargetsDeduplicated; + ++ProfileCounters.UrgentDeduplicatedByReason[ + UrgentReasonIndex(UrgentReplicationReason::Stagger)]; + return; + } + } + + size_t RetainedPendingCount = 0; + for (size_t Index = 0; Index < PendingUrgentDamageTargetCount; ++Index) { + const UrgentDamageTarget& Pending = PendingUrgentDamageTargets[Index]; + // A fresh authoritative interrupt supersedes any connection-local + // setup retry left from the previous interrupt for this behemoth. + if (Pending.Reason == UrgentReplicationReason::Stagger && + IdentitiesEqual(Pending.Identity, BehemothIdentity)) + continue; + PendingUrgentDamageTargets[RetainedPendingCount++] = Pending; + } + PendingUrgentDamageTargetCount = RetainedPendingCount; + if (PendingUrgentDamageTargetCount >= PendingUrgentDamageTargets.size()) { + ++ProfileCounters.UrgentDamageTargetsDropped; + return; + } + + PendingUrgentDamageTargets[PendingUrgentDamageTargetCount++] = { + BehemothIdentity, {}, GetTickCount64(), 0, + UrgentReplicationReason::Stagger + }; + ++ProfileCounters.UrgentDamageTargetsQueued; + ++ProfileCounters.UrgentQueuedByReason[ + UrgentReasonIndex(UrgentReplicationReason::Stagger)]; + } + + void NotifyBehemothInterruptClientRpc() { + ++ProfileCounters.BehemothInterruptClientNotifications; + } + + void QueueUrgentActor(AActor* Actor, UrgentReplicationReason Reason, + APlayerController* TargetController) { + const size_t ReasonIndex = UrgentReasonIndex(Reason); + const ObjectIdentity ActorIdentity = TrackObject(Actor); + UWorld* ExpectedWorld = NetDriver ? NetDriver->World : nullptr; + if (!ActorIdentity.Object || ResolveObject(ActorIdentity) != Actor || + (ExpectedWorld && GetActorWorld(Actor) != ExpectedWorld)) { + ++ProfileCounters.UrgentInvalidatedByReason[ReasonIndex]; + return; + } + + ObjectIdentity ConnectionIdentity{}; + if (TargetController) { + UNetConnection* Connection = FindConnectionForController(TargetController); + ConnectionIdentity = TrackObject(Connection); + if (!ConnectionIdentity.Object || ResolveObject(ConnectionIdentity) != Connection) { + ++ProfileCounters.UrgentInvalidatedByReason[ReasonIndex]; + return; + } + } + + for (size_t Index = 0; Index < PendingUrgentDamageTargetCount; ++Index) { + const UrgentDamageTarget& Pending = PendingUrgentDamageTargets[Index]; + if (Pending.Reason == Reason && + IdentitiesEqual(Pending.Identity, ActorIdentity) && + IdentitiesEqual(Pending.TargetConnection, ConnectionIdentity)) { + ++ProfileCounters.UrgentDeduplicatedByReason[ReasonIndex]; + ++ProfileCounters.UrgentDamageTargetsDeduplicated; + return; + } + } + + if (PendingUrgentDamageTargetCount >= PendingUrgentDamageTargets.size()) { + ++ProfileCounters.UrgentDroppedByReason[ReasonIndex]; + ++ProfileCounters.UrgentDamageTargetsDropped; + return; + } + + PendingUrgentDamageTargets[PendingUrgentDamageTargetCount++] = { + ActorIdentity, ConnectionIdentity, GetTickCount64(), 0, Reason + }; + ++ProfileCounters.UrgentQueuedByReason[ReasonIndex]; + ++ProfileCounters.UrgentDamageTargetsQueued; + } + + void RecordCombatEvent(CombatEvent Event, bool Succeeded, uint32_t Amount) { + const size_t Index = static_cast(Event); + if (Index >= CombatEventCount || Amount == 0) + return; + ProfileCounters.CombatEventCalls[Index] += Amount; + if (Succeeded) + ProfileCounters.CombatEventSuccesses[Index] += Amount; + } + + void RecordCombatTextQueue(const FArchonMultitypeCombatTextEntry& Entry) { + RecordCombatEvent(CombatEvent::CombatTextQueued, true); + ++ProfileCounters.CombatTextEntries; + ProfileCounters.CombatTextEstimatedBytes += sizeof(Entry); + } + + void RecordCombatTextMulticast(uint32_t EntryCount) { + RecordCombatEvent(CombatEvent::CombatTextMulticast, true, + EntryCount > 0 ? EntryCount : 1); + ProfileCounters.CombatTextMulticastEntries += EntryCount; + ProfileCounters.CombatTextMulticastEstimatedBytes += + static_cast(EntryCount) * sizeof(FArchonMultitypeCombatTextEntry); + } + + void NotifyClientLoadedWorld(APlayerController* PlayerController, FName WorldPackageName) { + if (!PlayerController) + return; + + UNetConnection* Connection = FindConnectionForController(PlayerController); + ConnectionBootstrapState* State = FindStateForConnection(Connection, PlayerController); + if (!Connection || !State) + return; + + const FName ExpectedPackage = ExpectedWorldPackageName(); + const ULONGLONG NowMs = GetTickCount64(); + const ULONGLONG ElapsedMs = NowMs - State->StartedAtMs; + if (ExpectedPackage.IsNone() || WorldPackageName != ExpectedPackage) { + ++ProfileCounters.LoadedWorldMismatched; + QueueConnectionEvent({ ConnectionEventType::LoadedWorldMismatch, Connection, CurrentNetworkTick(), + static_cast(WorldPackageName.ComparisonIndex), + static_cast(ExpectedPackage.ComparisonIndex), ElapsedMs }); + return; + } + + if (State->LoadedWorldAtMs && State->LastLoadedWorldPackage == WorldPackageName) + return; + + ++ProfileCounters.LoadedWorldAccepted; + ++ProfileCounters.LoadedWorldLatencySamples; + ProfileCounters.LoadedWorldLatencyMilliseconds += ElapsedMs; + QueueConnectionEvent({ ConnectionEventType::LoadedWorldAccepted, Connection, CurrentNetworkTick(), + static_cast(WorldPackageName.ComparisonIndex), WorldPackageName.Number, + ElapsedMs }); + + // This RPC is a seamless-travel lifecycle hint in this build. It may + // restart the bounded extra eligibility window, but never gates the + // ordinary replication pass. + RestartBootstrap(Connection, *State, NetDriver ? NetDriver->World : nullptr, + CurrentNetworkTick(), 3u, NowMs); + State->LoadedWorldAtMs = NowMs; + State->LastLoadedWorldPackage = WorldPackageName; + } + + void NotifyClientAcknowledgedPawn(APlayerController* PlayerController, APawn* Pawn) { + if (!PlayerController || !Pawn || PlayerController->Pawn != Pawn) + return; + + UNetConnection* Connection = FindConnectionForController(PlayerController); + ConnectionBootstrapState* State = FindStateForConnection(Connection, PlayerController); + if (!Connection || !State) + return; + + const ULONGLONG NowMs = GetTickCount64(); + if (!IdentityMatches(State->Pawn, Pawn)) + RestartBootstrap(Connection, *State, NetDriver ? NetDriver->World : nullptr, + CurrentNetworkTick(), 2u, NowMs); + if (State->PossessionAcknowledged) + return; + + State->PossessionAcknowledged = true; + State->PossessionAcknowledgedAtMs = NowMs; + ++ProfileCounters.PossessionAcknowledged; + QueueConnectionEvent({ ConnectionEventType::PossessionAcknowledged, Connection, CurrentNetworkTick(), + 0, 0, NowMs - State->StartedAtMs }); +} diff --git a/UndauntedInternalServer/Networking/Private/RuntimeState.inl b/UndauntedInternalServer/Networking/Private/RuntimeState.inl new file mode 100644 index 0000000..a726fd0 --- /dev/null +++ b/UndauntedInternalServer/Networking/Private/RuntimeState.inl @@ -0,0 +1,637 @@ +#include "../Networking.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SDK; + +namespace Networking { + UNetDriver* NetDriver = nullptr; + static std::string LastListenError; + static bool NativeDispatchOwnershipMismatchLogged = false; + static FName NativeGameNetDriverName{}; + static bool NativeOwnershipConflictLogged = false; + static LifecycleEventSink LifecycleSink = nullptr; + + void ConfigureLifecycleEventSink(LifecycleEventSink Sink) { + LifecycleSink = Sink; + } + + static void RecordLifecycleEvent(const std::string& EventName, + const std::string& FieldsJson) { + if (LifecycleSink != nullptr) + LifecycleSink(EventName, FieldsJson); + } + + namespace { + constexpr bool kAlwaysReplicateCriticalActors = true; + + constexpr uintptr_t OffsetActorGetWorldVTable = 0x150; + // AActor::CallPreReplication. Native ServerReplicateActors invokes this + // once per considered actor and replication frame before any connection + // serializes the actor. It refreshes ReplicatedMovement and calls the + // actor/component PreReplication virtuals. + constexpr uintptr_t OffsetActorCallPreReplication = 0x306B150; + constexpr uintptr_t OffsetCreateNamedNetDriver = 0x371A5E0; + constexpr uintptr_t OffsetFindNamedNetDriver = 0x37213A0; + constexpr uintptr_t OffsetSetNetDriverWorld = 0x3491890; + constexpr uintptr_t OffsetNetDriverNotify = 0x208; + constexpr uintptr_t OffsetNetDriverTickCount = 0x2AC; + constexpr uintptr_t OffsetConnectionState = 0x134; + constexpr uintptr_t OffsetChannelFlags = 0x30; + // Verified UIpNetDriver vtable slots in this build: +0x268 is the + // trivial IsAvailable method, while +0x280 is InitListen and invokes + // InitBase before creating/binding the socket. + constexpr uintptr_t OffsetNetDriverInitListen = 0x280; + constexpr uintptr_t OffsetCreateChannelByName = 0x3449E10; + constexpr uintptr_t OffsetSetChannelActor = 0x3283450; + constexpr uintptr_t OffsetActorChannelReplicationFlags = 0x90; + constexpr uintptr_t OffsetActorChannelReplicateActor = 0x327E860; + constexpr uintptr_t OffsetPlayerControllerUpdateCamera = 0x359F9D0; + constexpr uintptr_t OffsetActorIsNetRelevantFor = 0x307C970; + + constexpr uint32_t OpenChannelFlag = 1 << 1; + constexpr uint32_t ReplicationFlagNeedsTick = 2u; + constexpr uint32_t OpenConnectionState = 3; + constexpr uint32_t ChannelOpenAcknowledgedMask = 0x801u; + constexpr ULONGLONG BootstrapMinimumDurationMs = 3000; + constexpr ULONGLONG BootstrapMaximumDurationMs = 5000; + constexpr ULONGLONG BootstrapStableMs = 1000; + constexpr ULONGLONG DefaultConsiderCacheMaxAgeMs = 250; + constexpr size_t MaximumOwnerChainDepth = 8; + constexpr size_t MaximumScheduledActorsPerConnection = 4096; + constexpr size_t MaximumUrgentDamageTargets = 32; + constexpr ULONGLONG UrgentDamageTargetMaximumAgeMs = 1000; + constexpr uint8_t AdaptiveNoDataThreshold = 4; + constexpr float SchedulerMaximumFrequencyHz = 30.0f; + // Once bootstrap has completed, quiet non-core owner-chain actors may + // follow a conservative half-rate floor. Core presentation/combat + // classes remain at 30 Hz and any produced data restores full rate. + constexpr float OwnedCriticalAdaptiveFloorHz = 15.0f; + constexpr ULONGLONG StaticRelevancyAuditMs = 1000; + constexpr ULONGLONG IrrelevantRecheckMs = 250; + constexpr uint16_t ImmediateMetricOverflowSlot = + static_cast(ImmediateMetricTrackedClassCount); + constexpr uint16_t ImmediateMetricInvalidSlot = + static_cast(ImmediateMetricTrackedClassCount + 1); + constexpr uint16_t ImmediateMetricNotApplicable = 0xFFFF; + + using GetActorWorldFn = UWorld* (*)(AActor*); + using CallActorPreReplicationFn = void (*)(AActor*, UNetDriver*); + using CreateNamedNetDriverFn = uint8_t (*)(UEngine*, void*, FName, FName); + using FindNamedNetDriverFn = UNetDriver* (*)(UEngine*, void*, FName); + using SetNetDriverWorldFn = void (*)(UNetDriver*, UWorld*); + using InitListenFn = bool (*)(UNetDriver*, void*, FURL*, bool, FString*); + using CreateChannelByNameFn = UActorChannel* (*)(UNetConnection*, FName*, unsigned int, int); + using SetChannelActorFn = void (*)(UActorChannel*, AActor*, unsigned int); + using ReplicateActorFn = bool (*)(UActorChannel*); + using UpdateCameraFn = void (*)(APlayerController*); + using IsNetRelevantForFn = bool (*)(AActor*, AActor*, AActor*, const FVector*); + + enum class ReplicationBucket : uint8_t { + Immediate, + ArchonCharacter, + ArchonBehemoth, + Projectile, + PawnOrCharacter, + MovementActor + }; + + enum ReplicationClassFlag : uint32_t { + ClassPlayerController = 1u << 0, + ClassArchonCharacter = 1u << 1, + ClassArchonBehemoth = 1u << 2, + ClassArchonLantern = 1u << 3, + ClassProjectile = 1u << 4, + ClassPawn = 1u << 5, + ClassArchonEquipment = 1u << 6, + ClassArchonLoadout = 1u << 7, + ClassArchonAoe = 1u << 8, + ClassArchonBeam = 1u << 9, + ClassAbilityActor = 1u << 10 + }; + + struct TransformSample { + FVector Location{}; + FRotator Rotation{}; + bool Valid = false; + }; + + struct ObjectIdentity { + UObject* Object = nullptr; + int32_t Index = -1; + int32_t SerialNumber = 0; + }; + + struct ActorReplicationState { + ObjectIdentity Identity{}; + uint32_t LastPreReplicationTick = 0; + uint16_t ConsecutiveImmediateNoDataPasses = 0; + uint32_t ImmediatePassAttempts = 0; + uint32_t ImmediatePassSuccesses = 0; + uint32_t ImmediatePassNetworkTick = 0; + uint32_t ImmediatePassFinalizedTick = 0; + uint32_t ImmediateMetricGeneration = 0; + uint16_t ImmediateMetricSlot = ImmediateMetricNotApplicable; + }; + + struct ReplicationClassCache { + UClass* PlayerController = nullptr; + UClass* ArchonCharacter = nullptr; + UClass* ArchonBehemoth = nullptr; + UClass* ArchonLantern = nullptr; + UClass* ArchonEquipment = nullptr; + UClass* Projectile = nullptr; + UClass* Pawn = nullptr; + UClass* PlayerState = nullptr; + UClass* ArchonLoadout = nullptr; + UClass* ArchonInventory = nullptr; + UClass* ArchonBuff = nullptr; + UClass* ArchonAoe = nullptr; + UClass* ArchonBeam = nullptr; + UClass* AbilityActor = nullptr; + UClass* MonsterPartActor = nullptr; + }; + + struct ActorScheduleState; + + struct ReplicationCandidate { + AActor* Actor = nullptr; + ReplicationBucket Bucket = ReplicationBucket::Immediate; + uint32_t ClassFlags = 0; + bool IsPlayerController = false; + bool IsCritical = false; + ActorReplicationState* ReplicationState = nullptr; + uint64_t LastReplicatedConnectionPass = 0; + }; + + struct PrioritizedCandidate { + ReplicationCandidate* Candidate = nullptr; + float Priority = 0.0f; + ActorScheduleState* State = nullptr; + }; + + enum class ConnectionBootstrapPhase : uint8_t { + Bootstrapping, + Active + }; + + enum class InitializationActorRole : uint8_t { + Controller, + GameState, + PlayerState, + Pawn, + ViewTarget, + OwnedActor + }; + + struct CriticalActorState { + ObjectIdentity Identity{}; + InitializationActorRole Role = InitializationActorRole::OwnedActor; + uint8_t OwnerDepth = 0; + bool ChannelAcknowledged = false; + }; + + struct ConnectionBootstrapState { + ObjectIdentity Connection{}; + ObjectIdentity World{}; + ObjectIdentity Controller{}; + ObjectIdentity Pawn{}; + ConnectionBootstrapPhase Phase = ConnectionBootstrapPhase::Bootstrapping; + ULONGLONG StartedAtMs = 0; + ULONGLONG LoadedWorldAtMs = 0; + ULONGLONG PossessionAcknowledgedAtMs = 0; + ULONGLONG LastCriticalActorDiscoveredAtMs = 0; + FName LastLoadedWorldPackage{}; + bool PossessionAcknowledged = false; + std::unordered_map CriticalActors{}; + }; + + struct ImmediateClassRegistryEntry { + UClass* Class = nullptr; + int32_t Index = -1; + int32_t SerialNumber = 0; + }; + + struct ActorScheduleState { + ObjectIdentity Actor{}; + ObjectIdentity Owner{}; + AActor* OwnerPointer = nullptr; + TransformSample LastSample{}; + uint8_t ConsecutiveNoDataCalls = 0; + ULONGLONG LastConsideredAtMs = 0; + ULONGLONG LastAttemptAtMs = 0; + ULONGLONG LastSuccessfulDataAtMs = 0; + ULONGLONG NextFrequencyDeadlineMs = 0; + ULONGLONG NextRelevancyCheckMs = 0; + ULONGLONG LastRelevancyCheckMs = 0; + ENetDormancy LastDormancy = ENetDormancy::DORM_MAX; + bool OwnerTrackable = true; + bool RelevancyKnown = false; + bool LastRelevant = true; + bool TemporaryRetired = false; + bool TearOffRetired = false; + }; + + struct ConnectionScheduleState { + ObjectIdentity Connection{}; + ObjectIdentity World{}; + ObjectIdentity ViewTarget{}; + std::unordered_map Actors{}; + }; + + enum class LivePolicyDecision : uint8_t { + Eligible, + InvalidIdentity, + DriverMismatch, + Dormant, + NotRelevant, + NotDue, + TemporaryRetired, + TearOffRetired + }; + + enum class CacheRebuildReason : uint8_t { + Forced, + WorldChanged, + WorldIdentity, + MaximumAge, + LevelCount, + ActorCount + }; + + struct LivePolicyResult { + LivePolicyDecision Decision = LivePolicyDecision::InvalidIdentity; + ActorScheduleState* State = nullptr; + bool Moved = false; + }; + + struct UrgentDamageTarget { + ObjectIdentity Identity{}; + ObjectIdentity TargetConnection{}; + ULONGLONG QueuedAtMs = 0; + uint8_t SetupRetries = 0; + UrgentReplicationReason Reason = UrgentReplicationReason::Stagger; + }; + + struct UrgentCandidateEntry { + ReplicationCandidate* Candidate = nullptr; + ObjectIdentity Identity{}; + ObjectIdentity TargetConnection{}; + ULONGLONG QueuedAtMs = 0; + uint8_t SetupRetries = 0; + UrgentReplicationReason Reason = UrgentReplicationReason::Stagger; + }; + + struct UrgentCandidateBatch { + std::array Entries{}; + size_t Count = 0; + }; + } + + static uintptr_t BaseAddress = 0x0; + static std::vector ConsiderCache{}; + static std::unordered_map CandidateActorLookup{}; + static std::vector PriorityScratch{}; + static std::vector CachedLevelActorCounts{}; + static UWorld* CachedWorld = nullptr; + static ObjectIdentity CachedWorldIdentity{}; + static ULONGLONG LastConsiderCacheBuildMs = 0; + static std::unordered_map ScratchActorChannels{}; + static std::unordered_map ActorReplicationStates{}; + static std::unordered_map ConnectionBootstrapStates{}; + static std::unordered_map ConnectionScheduleStates{}; + static std::unordered_set CurrentConnections{}; + static std::vector LiveConnections{}; + static ProfilingCounters ProfileCounters{}; + static uint32_t CurrentSchedulerActorStates = 0; + static IsNetRelevantForFn NativeIsNetRelevantFor = nullptr; + static CallActorPreReplicationFn NativeCallActorPreReplication = nullptr; + static bool NativeRelevancySignatureValid = false; + static bool NativePreReplicationSignatureValid = false; + static ULONGLONG ConsiderCacheMaxAgeMs = DefaultConsiderCacheMaxAgeMs; + static bool ConsiderCacheDirtyForNextConnection = true; + static bool ActorPropertyDistributionDirty = true; + static uint32_t ImmediateMetricGeneration = 1; + static std::array + ImmediateClassRegistry{}; + static std::array PendingUrgentDamageTargets{}; + static size_t PendingUrgentDamageTargetCount = 0; + static uint64_t CurrentConnectionPass = 0; + + enum class ConnectionEventType : uint8_t { + Open, + Close, + BootstrapStarted, + BootstrapRestarted, + LoadedWorldAccepted, + LoadedWorldMismatch, + PossessionAcknowledged, + CriticalActorDiscovered, + CriticalChannelAcknowledged, + BootstrapCompleted, + BootstrapDeadline, + NativePostLogin, + NativeReciprocalRepair, + NativeDispatchConflict + }; + struct ConnectionEvent { + ConnectionEventType Type{}; + UNetConnection* Connection = nullptr; + uint32_t NetworkTick = 0; + uint32_t ValueA = 0; + uint32_t ValueB = 0; + uint64_t ElapsedMilliseconds = 0; + }; + static std::array PendingConnectionEvents{}; + static size_t PendingConnectionEventCount = 0; + + static int64_t PerformanceTimestamp() { + LARGE_INTEGER Value{}; + QueryPerformanceCounter(&Value); + return Value.QuadPart; + } + + static uint64_t ElapsedMicroseconds(int64_t StartedAt) { + static const int64_t Frequency = []() { + LARGE_INTEGER Value{}; + QueryPerformanceFrequency(&Value); + return Value.QuadPart; + }(); + + const int64_t Elapsed = PerformanceTimestamp() - StartedAt; + return Frequency > 0 && Elapsed > 0 + ? static_cast((Elapsed * 1000000ll) / Frequency) + : 0ull; + } + + static std::string PointerValue(const void* Value) { + return std::to_string(reinterpret_cast(Value)); + } + + static const char* InitializationRoleName(uint32_t Value) { + switch (static_cast(Value)) { + case InitializationActorRole::Controller: return "controller"; + case InitializationActorRole::GameState: return "game_state"; + case InitializationActorRole::PlayerState: return "player_state"; + case InitializationActorRole::Pawn: return "pawn"; + case InitializationActorRole::ViewTarget: return "view_target"; + default: return "owned_actor"; + } + } + + static bool IsOpenConnection(UNetConnection* Connection) { + return Connection + && Connection->OwningActor + && *reinterpret_cast((uintptr_t)Connection + OffsetConnectionState) == OpenConnectionState; + } + + static ObjectIdentity TrackObject(UObject* Object) { + if (!Object || Object->Index < 0) + return {}; + + FUObjectItem* Item = UObject::GObjects->GetItemByIndex(Object->Index); + if (!Item || Item->Object != Object) + return {}; + + return { Object, Object->Index, Item->SerialNumber }; + } + + static UObject* ResolveObject(const ObjectIdentity& Identity) { + if (!Identity.Object || Identity.Index < 0) + return nullptr; + + FUObjectItem* Item = UObject::GObjects->GetItemByIndex(Identity.Index); + if (!Item || Item->Object != Identity.Object || Item->SerialNumber != Identity.SerialNumber || + (static_cast(Item->Flags) & 0x30000000u) != 0) + return nullptr; + + UObject* Object = Item->Object; + if ((Object->Flags & EObjectFlags::BeginDestroyed) || (Object->Flags & EObjectFlags::FinishDestroyed)) + return nullptr; + + return Object; + } + + static bool IdentityMatches(const ObjectIdentity& Stored, UObject* Current) { + if (!Current) + return !Stored.Object; + + const ObjectIdentity Fresh = TrackObject(Current); + return Fresh.Object == Current + && Stored.Object == Fresh.Object + && Stored.Index == Fresh.Index + && Stored.SerialNumber == Fresh.SerialNumber; + } + + static bool IdentitiesEqual(const ObjectIdentity& Left, const ObjectIdentity& Right) { + return Left.Object == Right.Object + && Left.Index == Right.Index + && Left.SerialNumber == Right.SerialNumber; + } + + static size_t UrgentReasonIndex(UrgentReplicationReason Reason) { + const size_t Index = static_cast(Reason); + return Index < UrgentReplicationReasonCount ? Index : 0; + } + + static bool IsStableIdentity(const ObjectIdentity& Identity, const UObject* Expected) { + return Expected && ResolveObject(Identity) == Expected; + } + + static ConnectionScheduleState MakeConnectionScheduleState( + UNetConnection* Connection, + UWorld* World + ) { + ConnectionScheduleState State{}; + State.Connection = TrackObject(Connection); + State.World = TrackObject(World); + State.ViewTarget = TrackObject(Connection ? Connection->ViewTarget : nullptr); + State.Actors.reserve((std::min)(MaximumScheduledActorsPerConnection, + (std::max)(size_t{ 256 }, ConsiderCache.size()))); + return State; + } + + static void ClearConnectionScheduleState(UNetConnection* Connection) { + const auto It = ConnectionScheduleStates.find(Connection); + if (It == ConnectionScheduleStates.end()) + return; + + const uint32_t Removed = static_cast(It->second.Actors.size()); + CurrentSchedulerActorStates = Removed <= CurrentSchedulerActorStates + ? CurrentSchedulerActorStates - Removed + : 0; + ConnectionScheduleStates.erase(It); + } + + static uint16_t RegisterImmediateClass(UClass* Class) { + const ObjectIdentity Identity = TrackObject(Class); + if (!Identity.Object || ResolveObject(Identity) != Class) + return ImmediateMetricInvalidSlot; + + const uint32_t IdentityHash = + static_cast(Identity.Index) * 0x9E3779B1u + ^ static_cast(Identity.SerialNumber); + for (size_t Probe = 0; Probe < ImmediateClassRegistry.size(); ++Probe) { + const size_t Slot = (IdentityHash + Probe) % ImmediateClassRegistry.size(); + ImmediateClassRegistryEntry& Entry = ImmediateClassRegistry[Slot]; + if (Entry.Index == Identity.Index && Entry.SerialNumber == Identity.SerialNumber) + return static_cast(Slot); + if (Entry.Index >= 0) + continue; + + Entry.Class = Class; + Entry.Index = Identity.Index; + Entry.SerialNumber = Identity.SerialNumber; + return static_cast(Slot); + } + return ImmediateMetricOverflowSlot; + } + + static size_t ImmediateNoDataBand(uint16_t Streak) { + if (Streak <= 1) return 0; + if (Streak <= 3) return 1; + if (Streak <= 15) return 2; + if (Streak <= 63) return 3; + if (Streak <= 255) return 4; + return 5; + } + + static size_t NetFrequencyBand(float Frequency) { + if (!std::isfinite(Frequency)) return 0; + if (Frequency <= 0.0f) return 1; + if (Frequency <= 2.0f) return 2; + if (Frequency <= 5.0f) return 3; + if (Frequency <= 10.0f) return 4; + if (Frequency <= 15.0f) return 5; + if (Frequency <= 30.0f) return 6; + if (Frequency <= 60.0f) return 7; + if (Frequency <= 100.0f) return 8; + return 9; + } + + static size_t NetDormancyBand(ENetDormancy Dormancy) { + const size_t Value = static_cast(Dormancy); + return Value < NetDormancyBandCount - 1 ? Value : NetDormancyBandCount - 1; + } + + static void RecordActorPropertyDistribution( + AActor* Actor, + ReplicationBucket Bucket, + bool IsCritical + ) { + if (!Actor) + return; + + ++ProfileCounters.ActorPropertySamples; + ++ProfileCounters.NetUpdateFrequencyBands[NetFrequencyBand(Actor->NetUpdateFrequency)]; + ++ProfileCounters.MinNetUpdateFrequencyBands[NetFrequencyBand(Actor->MinNetUpdateFrequency)]; + ++ProfileCounters.NetDormancyBands[NetDormancyBand(Actor->NetDormancy)]; + if (Actor->Owner) ++ProfileCounters.OwnedActorSamples; + if (Actor->bReplicateMovement) ++ProfileCounters.ReplicatedMovementSamples; + if (IsCritical) ++ProfileCounters.CriticalActorSamples; + if (Bucket == ReplicationBucket::Immediate) ++ProfileCounters.ImmediateActorSamples; + if (Actor->bNetTemporary) ++ProfileCounters.NetTemporarySamples; + if (Actor->bNetStartup) ++ProfileCounters.NetStartupSamples; + if (Actor->bNetLoadOnClient) ++ProfileCounters.NetLoadOnClientSamples; + if (Actor->bOnlyRelevantToOwner) ++ProfileCounters.OnlyRelevantToOwnerSamples; + if (Actor->bAlwaysRelevant) ++ProfileCounters.AlwaysRelevantSamples; + if (Actor->bNetUseOwnerRelevancy) ++ProfileCounters.OwnerRelevancySamples; + if (Actor->bTearOff) ++ProfileCounters.TearOffSamples; + } + + static uint16_t ResolveImmediateMetricSlot( + AActor* Actor, + ActorReplicationState* State + ) { + if (!Actor || !Actor->Class || !State) + return ImmediateMetricInvalidSlot; + if (State->ImmediateMetricGeneration != ImmediateMetricGeneration) { + State->ImmediateMetricGeneration = ImmediateMetricGeneration; + State->ImmediateMetricSlot = RegisterImmediateClass(Actor->Class); + } + return State->ImmediateMetricSlot; + } + + static void RecordClassReplicationResult( + AActor* Actor, + ActorReplicationState* State, + ReplicationBucket Bucket, + bool ProducedData + ) { + uint16_t MetricSlot = ResolveImmediateMetricSlot(Actor, State); + if (MetricSlot >= ProfileCounters.ImmediateClasses.size()) + MetricSlot = ImmediateMetricInvalidSlot; + + ImmediateClassCounters& Counters = ProfileCounters.ImmediateClasses[MetricSlot]; + ++Counters.Attempts; + if (Bucket == ReplicationBucket::Immediate && State && + State->ImmediatePassAttempts < (std::numeric_limits::max)()) + ++State->ImmediatePassAttempts; + if (ProducedData) { + ++Counters.Successes; + if (Bucket == ReplicationBucket::Immediate && State && + State->ImmediatePassSuccesses < (std::numeric_limits::max)()) { + ++State->ImmediatePassSuccesses; + } + } + } + + static void PopulateImmediateClassNames(ProfilingCounters& Snapshot) { + for (size_t Index = 0; Index < ImmediateClassRegistry.size(); ++Index) { + const ImmediateClassRegistryEntry& Entry = ImmediateClassRegistry[Index]; + if (Entry.Index < 0) + continue; + + // Name work is intentionally deferred until after the replication + // pass. Resolve the interval-scoped class identity again rather than + // trusting a pointer retained by the hot-path registry. + const ObjectIdentity Identity{ + Entry.Class, + Entry.Index, + Entry.SerialNumber + }; + UObject* Resolved = ResolveObject(Identity); + const std::string Name = Resolved + ? static_cast(Resolved)->GetName() + : "invalidated"; + auto& Destination = Snapshot.ImmediateClasses[Index].ClassName; + const size_t CopyLength = (std::min)(Name.size(), Destination.size() - 1); + if (CopyLength) + std::memcpy(Destination.data(), Name.data(), CopyLength); + } + + constexpr char OverflowName[] = "overflow"; + constexpr char InvalidName[] = "invalid"; + std::memcpy(Snapshot.ImmediateClasses[ImmediateMetricOverflowSlot].ClassName.data(), + OverflowName, sizeof(OverflowName)); + std::memcpy(Snapshot.ImmediateClasses[ImmediateMetricInvalidSlot].ClassName.data(), + InvalidName, sizeof(InvalidName)); + } + + static void ResetImmediateClassRegistry() { + ImmediateClassRegistry = {}; + if (++ImmediateMetricGeneration == 0) + ImmediateMetricGeneration = 1; + } + + static ConnectionBootstrapState MakeConnectionBootstrapState(UNetConnection* Connection, UWorld* World) { + ConnectionBootstrapState State{}; + State.Connection = TrackObject(Connection); + State.World = TrackObject(World); + State.Controller = TrackObject(Connection ? Connection->PlayerController : nullptr); + State.Pawn = TrackObject(Connection && Connection->PlayerController ? Connection->PlayerController->Pawn : nullptr); + State.Phase = ConnectionBootstrapPhase::Bootstrapping; + State.StartedAtMs = GetTickCount64(); + State.LastCriticalActorDiscoveredAtMs = State.StartedAtMs; + State.CriticalActors.reserve(32); + return State; +} diff --git a/UndauntedInternalServer/SDK/Basic.hpp b/UndauntedInternalServer/SDK/Basic.hpp index 478f897..69d9870 100644 --- a/UndauntedInternalServer/SDK/Basic.hpp +++ b/UndauntedInternalServer/SDK/Basic.hpp @@ -222,7 +222,10 @@ struct FUObjectItem final { public: class UObject* Object; // 0x0000(0x0008)(NOT AUTO-GENERATED PROPERTY) - uint8 Pad_8[0x10]; // 0x0008(0x0010)(Fixing Struct Size After Last Property [ Dumper-7 ]) + int32 Flags; // 0x0008(0x0004)(NOT AUTO-GENERATED PROPERTY) + int32 ClusterRootIndex; // 0x000C(0x0004)(NOT AUTO-GENERATED PROPERTY) + int32 SerialNumber; // 0x0010(0x0004)(NOT AUTO-GENERATED PROPERTY) + uint8 Pad_14[0x4]; // 0x0014(0x0004)(Fixing Struct Size After Last Property [ Dumper-7 ]) }; DUMPER7_ASSERTS_FUObjectItem; @@ -269,6 +272,18 @@ class TUObjectArray final return ChunkPtr[InChunkIdx].Object; } + + inline FUObjectItem* GetItemByIndex(const int32 Index) const + { + const int32 ChunkIndex = Index / ElementsPerChunk; + const int32 InChunkIdx = Index % ElementsPerChunk; + + if (Index < 0 || ChunkIndex >= NumChunks || Index >= NumElements) + return nullptr; + + FUObjectItem* ChunkPtr = GetDecrytedObjPtr()[ChunkIndex]; + return ChunkPtr ? &ChunkPtr[InChunkIdx] : nullptr; + } }; DUMPER7_ASSERTS_TUObjectArray; diff --git a/UndauntedInternalServer/SDK/CoreUObject_functions.cpp b/UndauntedInternalServer/SDK/CoreUObject_functions.cpp index 428f214..4a47ae7 100644 --- a/UndauntedInternalServer/SDK/CoreUObject_functions.cpp +++ b/UndauntedInternalServer/SDK/CoreUObject_functions.cpp @@ -194,7 +194,6 @@ class UFunction* UClass::GetFunction(const char* ClassName, const char* FuncName { for(const UStruct* Clss = this; Clss; Clss = Clss->SuperStruct) { - std::cout << Clss->GetName() << std::endl; if (Clss->GetName() != ClassName) continue; diff --git a/UndauntedInternalServer/UndauntedInternalServer.vcxproj b/UndauntedInternalServer/UndauntedInternalServer.vcxproj index 171d916..73f9866 100644 --- a/UndauntedInternalServer/UndauntedInternalServer.vcxproj +++ b/UndauntedInternalServer/UndauntedInternalServer.vcxproj @@ -121,18 +121,29 @@ Level3 + Full + Speed + AnySuitable true true + true + true + false + Fast true NDEBUG;UNDAUNTEDINTERNALSERVER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h stdcpplatest + /FS /Gw /Zc:inline %(AdditionalOptions) Windows true + UseLinkTimeCodeGeneration + true + true false @@ -140,6 +151,8 @@ + + @@ -148,7 +161,8 @@ - + + @@ -163,4 +177,4 @@ - \ No newline at end of file + diff --git a/UndauntedInternalServer/UndauntedInternalServer.vcxproj.filters b/UndauntedInternalServer/UndauntedInternalServer.vcxproj.filters index fa8b3ef..9be5a22 100644 --- a/UndauntedInternalServer/UndauntedInternalServer.vcxproj.filters +++ b/UndauntedInternalServer/UndauntedInternalServer.vcxproj.filters @@ -30,6 +30,30 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -59,7 +83,10 @@ SDK - + + Source Files + + Source Files @@ -84,4 +111,4 @@ SDK - \ No newline at end of file + diff --git a/UndauntedInternalServer/dllmain.cpp b/UndauntedInternalServer/dllmain.cpp index b4bbe81..7c98a6b 100644 --- a/UndauntedInternalServer/dllmain.cpp +++ b/UndauntedInternalServer/dllmain.cpp @@ -1,18 +1,23 @@ #include #include #include +#include #include #include #include #include #include #include +#include + +#pragma comment(lib, "winhttp.lib") #include "framework.h" #include "SDK.hpp" #include "MinHook/MinHook.h" #include "constants.h" #include "Networking.h" +#include "Networking/NetworkLifecycle.h" #include "SDK/GameplayAbilities_parameters.hpp" #include "SDK/Archon_parameters.hpp" @@ -32,6 +37,10 @@ namespace Globals { const wchar_t* ExpectedPlayerString = nullptr; int Port = 0; const wchar_t* MyIpAndPort = nullptr; + std::wstring GameserverId; + std::wstring ReadyCallbackUrl; + std::wstring ReadyCallbackToken; + std::wstring LifecycleCallbackUrl; std::wstring MetagameAddress; bool EnableLogging = true; @@ -222,6 +231,107 @@ __declspec(dllexport) const char* DummyLinkFunc() { return "mrow :3"; } +static std::string WideToUtf8(const std::wstring& Value) { + if (Value.empty()) + return ""; + + int Size = WideCharToMultiByte(CP_UTF8, 0, Value.c_str(), -1, nullptr, 0, nullptr, nullptr); + + if (Size <= 0) + return ""; + + std::string Result(Size, '\0'); + WideCharToMultiByte(CP_UTF8, 0, Value.c_str(), -1, Result.data(), Size, nullptr, nullptr); + Result.pop_back(); + + return Result; +} + +static bool PostJsonCallback(const std::wstring& Url, const std::wstring& TokenHeader, + const std::wstring& Token, const std::string& Body) { + URL_COMPONENTS UrlComponents = {}; + wchar_t HostName[256] = {}; + wchar_t UrlPath[1024] = {}; + wchar_t ExtraInfo[1024] = {}; + + UrlComponents.dwStructSize = sizeof(UrlComponents); + UrlComponents.lpszHostName = HostName; + UrlComponents.dwHostNameLength = ARRAYSIZE(HostName); + UrlComponents.lpszUrlPath = UrlPath; + UrlComponents.dwUrlPathLength = ARRAYSIZE(UrlPath); + UrlComponents.lpszExtraInfo = ExtraInfo; + UrlComponents.dwExtraInfoLength = ARRAYSIZE(ExtraInfo); + + if (!WinHttpCrackUrl(Url.c_str(), 0, 0, &UrlComponents)) { + return false; + } + + DWORD RequestFlags = UrlComponents.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0; + + HINTERNET Session = WinHttpOpen(L"UndauntedInternalServer/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + + if (!Session) + return false; + WinHttpSetTimeouts(Session, 2000, 2000, 2000, 2000); + + HINTERNET Connect = WinHttpConnect(Session, HostName, UrlComponents.nPort, 0); + + if (!Connect) { + WinHttpCloseHandle(Session); + return false; + } + + std::wstring RequestPath = std::wstring(UrlPath) + std::wstring(ExtraInfo); + + HINTERNET Request = WinHttpOpenRequest(Connect, L"POST", RequestPath.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, RequestFlags); + + if (!Request) { + WinHttpCloseHandle(Connect); + WinHttpCloseHandle(Session); + return false; + } + + const std::wstring Headers = L"Content-Type: application/json\r\n" + + TokenHeader + L": " + Token + L"\r\n"; + + BOOL Sent = WinHttpSendRequest(Request, Headers.c_str(), (DWORD)-1L, (LPVOID)Body.data(), (DWORD)Body.size(), (DWORD)Body.size(), 0); + BOOL Received = Sent ? WinHttpReceiveResponse(Request, nullptr) : FALSE; + DWORD StatusCode = 0; + DWORD StatusCodeSize = sizeof(StatusCode); + + if (Received) { + WinHttpQueryHeaders(Request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &StatusCode, &StatusCodeSize, WINHTTP_NO_HEADER_INDEX); + } + + WinHttpCloseHandle(Request); + WinHttpCloseHandle(Connect); + WinHttpCloseHandle(Session); + + return Received && StatusCode >= 200 && StatusCode < 300; +} + +static bool SendReadyCallback() { + if (Globals::GameserverId.empty() || Globals::ReadyCallbackUrl.empty() || + Globals::ReadyCallbackToken.empty()) + return true; + const std::string Body = "{\"id\":\"" + WideToUtf8(Globals::GameserverId) + + "\",\"port\":" + std::to_string(Globals::Port) + + ",\"pid\":" + std::to_string(GetCurrentProcessId()) + + ",\"ready\":true}"; + return PostJsonCallback(Globals::ReadyCallbackUrl, + L"x-undaunted-ready-token", Globals::ReadyCallbackToken, Body); +} + +static bool StartLifecycleHeartbeat() { + Networking::Lifecycle::LifecycleConfig Config{}; + Config.callbackUrl = Globals::LifecycleCallbackUrl; + Config.callbackToken = Globals::ReadyCallbackToken; + Config.serverId = Globals::GameserverId; + Config.port = static_cast((std::max)(0, Globals::Port)); + Config.callback = PostJsonCallback; + return Networking::Lifecycle::Start(Config); +} + void MainThread() { while (!UWorld::GetWorld()) { if (Globals::AmServer) { @@ -232,9 +342,9 @@ void MainThread() { } } - Sleep(3 * 1000); - if (!Globals::AmServer) { + Sleep(3 * 1000); + UEngine* Engine = UEngine::GetEngine(); UInputSettings::GetDefaultObj()->ConsoleKeys[0].KeyName = UKismetStringLibrary::Conv_StringToName(L"F2"); @@ -319,6 +429,53 @@ bool EnableWatchdog = true; void* OrigGameEngineTick = nullptr; +static float ListenReadinessElapsed = 0.0f; +static int ListenReadinessStableTicks = 0; +static bool ListenReadinessFallbackLogged = false; + +static bool IsServerReadyToListen() { + UWorld* World = UWorld::GetWorld(); + UEngine* Engine = UEngine::GetEngine(); + + if (!World || !Engine || !World->NetworkNotify) + return false; + + if (World->Levels.Num() <= 0) + return false; + + for (ULevel* Level : World->Levels) { + if (Level) + return true; + } + + return false; +} + +static bool ShouldAttemptListen(float DeltaTime) { + ListenReadinessElapsed += DeltaTime; + + if (IsServerReadyToListen()) { + ListenReadinessStableTicks++; + + if (ListenReadinessStableTicks >= 2) + return true; + } + else { + ListenReadinessStableTicks = 0; + } + + if (ListenReadinessElapsed >= 10.0f) { + if (!ListenReadinessFallbackLogged && Globals::EnableLogging) { + ListenReadinessFallbackLogged = true; + std::cout << "Server readiness fallback elapsed; attempting listen" << std::endl; + } + + return true; + } + + return false; +} + void GameEngineTickHook(UGameEngine* GameEngine, float DeltaTime, char CanRender) { reinterpret_cast(OrigGameEngineTick)(GameEngine, DeltaTime, CanRender); @@ -345,13 +502,23 @@ void GameEngineTickHook(UGameEngine* GameEngine, float DeltaTime, char CanRender if (Globals::Listening) { Networking::TickNetworking(); + UWorld* World = UWorld::GetWorld(); + const uint32_t RawConnections = Networking::NetDriver + ? static_cast((std::max)(0, + Networking::NetDriver->ClientConnections.Num())) + : 0; + Networking::Lifecycle::ObserveConnections( + static_cast(Networking::GetLiveConnections().size()), + RawConnections, World); } - if (Globals::DoListen) { + if (Globals::DoListen && ShouldAttemptListen(DeltaTime)) { Globals::DoListen = false; - Networking::Listen(UEngine::GetEngine(), Globals::Port); + Globals::Listening = Networking::Listen(UEngine::GetEngine(), Globals::Port); - Globals::Listening = true; + if (Globals::Listening && SendReadyCallback()) { + StartLifecycleHeartbeat(); + } } if (Globals::Listening && Networking::NetDriver) { @@ -921,6 +1088,21 @@ void Init() { Globals::ExpectedPlayerString = Args[6]; Globals::MyIpAndPort = Args[7]; + if (NumArgs > 11 && Args[8][0] != L'-') { + Globals::GameserverId = Args[8]; + Globals::ReadyCallbackUrl = Args[9]; + Globals::ReadyCallbackToken = Args[10]; + } + constexpr std::wstring_view LifecyclePrefix = + L"-undauntedLifecycleCallbackUrl="; + for (int Index = 11; Index < NumArgs; ++Index) { + const std::wstring Argument = Args[Index] ? Args[Index] : L""; + if (Argument.starts_with(LifecyclePrefix)) { + Globals::LifecycleCallbackUrl = + Argument.substr(LifecyclePrefix.size()); + } + } + if (Globals::Port >= 8776) { EnableWatchdog = false; Globals::EnableLogging = true; diff --git a/UndauntedMetagame/build.cjs b/UndauntedMetagame/build.cjs new file mode 100644 index 0000000..d223f2f --- /dev/null +++ b/UndauntedMetagame/build.cjs @@ -0,0 +1,18 @@ +const path = require("path"); +const { build } = require("esbuild"); + +build({ + absWorkingDir: __dirname, + entryPoints: ["./src/server.ts"], + bundle: true, + platform: "node", + target: "es2022", + format: "cjs", + packages: "external", + minify: true, + legalComments: "none", + outfile: "./dist/server.js", +}).catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/UndauntedMetagame/package-lock.json b/UndauntedMetagame/package-lock.json deleted file mode 100644 index af76834..0000000 --- a/UndauntedMetagame/package-lock.json +++ /dev/null @@ -1,3420 +0,0 @@ -{ - "name": "undauntedmetagame", - "version": "0.0.3", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "undauntedmetagame", - "version": "0.0.3", - "license": "AGPL-3.0-only", - "dependencies": { - "better-sqlite3": "^12.11.1", - "dotenv": "^17.4.2", - "drizzle-orm": "0.45.2", - "express": "5.2.1", - "jsonwebtoken": "^9.0.3", - "pino": "^10.3.1", - "pino-pretty": "^13.1.3" - }, - "devDependencies": { - "@types/express": "5.0.6", - "@types/jsonwebtoken": "^9.0.10", - "@types/node": "26.0.0", - "drizzle-kit": "0.31.10", - "tsx": "4.22.4", - "typescript": "6.0.3" - } - }, - "node_modules/@drizzle-team/brocli": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", - "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "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/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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/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/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", - "peer": true, - "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/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "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/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/drizzle-kit": { - "version": "0.31.10", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", - "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@drizzle-team/brocli": "^0.10.2", - "@esbuild-kit/esm-loader": "^2.5.5", - "esbuild": "^0.25.4", - "tsx": "^4.21.0" - }, - "bin": { - "drizzle-kit": "bin.cjs" - } - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fast-copy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz", - "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==", - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/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/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-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/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "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/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "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/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": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "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/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/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-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/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/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/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/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/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-pretty": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", - "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^4.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "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/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.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/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/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/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/rc/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/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/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/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/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-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==", - "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/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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.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/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/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/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "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/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/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", - "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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/tsx/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/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/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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" - } - } -} diff --git a/UndauntedMetagame/package.json b/UndauntedMetagame/package.json index 1660a5a..87e5d1a 100644 --- a/UndauntedMetagame/package.json +++ b/UndauntedMetagame/package.json @@ -6,7 +6,17 @@ "author": "gwog :3", "scripts": { "dev": "tsx watch --env-file=.env src/server.ts", - "build": "tsc", + "build": "node build.cjs", + "test:xmpp": "tsx src/xmpp/selftest.ts", + "test:stomp": "tsx src/stomp/selftest.ts", + "test:friends": "tsx src/selftests/friends.ts", + "test:party": "tsx src/selftests/party.ts", + "test:loadout": "tsx src/selftests/loadout.ts", + "test:mastery": "tsx src/selftests/mastery.ts", + "test:store": "tsx src/selftests/store.ts", + "test:huntpass": "tsx src/selftests/huntpass.ts", + "test:inventory": "tsx src/selftests/inventory.ts", + "test": "tsx src/selftests/run.ts", "start": "node --env-file=.env dist/server.js", "db:generate": "drizzle-kit generate" }, @@ -17,13 +27,17 @@ "express": "5.2.1", "jsonwebtoken": "^9.0.3", "pino": "^10.3.1", - "pino-pretty": "^13.1.3" + "pino-pretty": "^13.1.3", + "saxes": "^6.0.0", + "ws": "^8.21.0" }, "devDependencies": { "@types/express": "5.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/node": "26.0.0", + "@types/ws": "^8.18.1", "drizzle-kit": "0.31.10", + "esbuild": "0.28.1", "tsx": "4.22.4", "typescript": "6.0.3" } diff --git a/UndauntedMetagame/tsconfig.json b/UndauntedMetagame/tsconfig.json index 814fd9e..6fd64a1 100644 --- a/UndauntedMetagame/tsconfig.json +++ b/UndauntedMetagame/tsconfig.json @@ -9,7 +9,9 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo" }, "include": ["src/**/*.ts"] -} \ No newline at end of file +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3c50120 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "packageManager": "pnpm@11.13.0", + "scripts": { + "build:metagame": "pnpm --dir UndauntedMetagame run build", + "build:deploy": "pnpm --dir UndauntedDeployServer run build", + "build:internal": "powershell -NoProfile -ExecutionPolicy Bypass -File .vscode/Build-DeployReleaseDll.ps1", + "build:servers": "pnpm --parallel --aggregate-output --dir UndauntedMetagame run build --dir UndauntedDeployServer run build", + "build:all": "powershell -NoProfile -Command \"$server = Start-Process pnpm -ArgumentList 'run','build:servers' -NoNewWindow -PassThru; $internal = Start-Process pnpm -ArgumentList 'run','build:internal' -NoNewWindow -PassThru; Wait-Process -InputObject $server,$internal; if ($server.ExitCode -ne 0 -or $internal.ExitCode -ne 0) { exit 1 }\"" + }, + "devDependencies": {} +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..777ec6c --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2238 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + UndauntedDeployServer: + dependencies: + better-sqlite3: + specifier: ^12.11.1 + version: 12.11.1 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + drizzle-orm: + specifier: 0.45.2 + version: 0.45.2(better-sqlite3@12.11.1) + express: + specifier: 5.2.1 + version: 5.2.1 + jsonwebtoken: + specifier: ^9.0.3 + version: 9.0.3 + pino: + specifier: ^10.3.1 + version: 10.3.1 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 + devDependencies: + '@types/express': + specifier: 5.0.6 + version: 5.0.6 + '@types/jsonwebtoken': + specifier: ^9.0.10 + version: 9.0.10 + '@types/node': + specifier: 26.0.0 + version: 26.0.0 + drizzle-kit: + specifier: 0.31.10 + version: 0.31.10 + esbuild: + specifier: 0.28.1 + version: 0.28.1 + tsx: + specifier: 4.22.4 + version: 4.22.4 + typescript: + specifier: 6.0.3 + version: 6.0.3 + + UndauntedMetagame: + dependencies: + better-sqlite3: + specifier: ^12.11.1 + version: 12.11.1 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + drizzle-orm: + specifier: 0.45.2 + version: 0.45.2(better-sqlite3@12.11.1) + express: + specifier: 5.2.1 + version: 5.2.1 + jsonwebtoken: + specifier: ^9.0.3 + version: 9.0.3 + pino: + specifier: ^10.3.1 + version: 10.3.1 + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 + saxes: + specifier: ^6.0.0 + version: 6.0.0 + ws: + specifier: ^8.21.0 + version: 8.21.0 + devDependencies: + '@types/express': + specifier: 5.0.6 + version: 5.0.6 + '@types/jsonwebtoken': + specifier: ^9.0.10 + version: 9.0.10 + '@types/node': + specifier: 26.0.0 + version: 26.0.0 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 + drizzle-kit: + specifier: 0.31.10 + version: 0.31.10 + esbuild: + specifier: 0.28.1 + version: 0.28.1 + tsx: + specifier: 4.22.4 + version: 4.22.4 + typescript: + specifier: 6.0.3 + version: 6.0.3 + +packages: + + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.hirok.io' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.hirok.io' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/express-serve-static-core@5.1.2': + resolution: {integrity: sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@26.0.0': + resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.11.1: + resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + drizzle-kit@0.31.10: + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} + hasBin: true + + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-copy@4.0.4: + resolution: {integrity: sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-abi@3.94.0: + resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==} + engines: {node: '>=10'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + +snapshots: + + '@drizzle-team/brocli@0.10.2': {} + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.14.0 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@pinojs/redact@0.4.0': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 26.0.0 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 26.0.0 + + '@types/express-serve-static-core@5.1.2': + dependencies: + '@types/node': 26.0.0 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.2 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 26.0.0 + + '@types/ms@2.1.0': {} + + '@types/node@26.0.0': + dependencies: + undici-types: 8.3.0 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 26.0.0 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 26.0.0 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 26.0.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + atomic-sleep@1.0.0: {} + + base64-js@1.5.1: {} + + better-sqlite3@12.11.1: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chownr@1.1.4: {} + + colorette@2.0.20: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + dateformat@4.6.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + dotenv@17.4.2: {} + + drizzle-kit@0.31.10: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + tsx: 4.22.4 + + drizzle-orm@0.45.2(better-sqlite3@12.11.1): + optionalDependencies: + better-sqlite3: 12.11.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.28.1: + 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 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + expand-template@2.0.3: {} + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-copy@4.0.4: {} + + fast-safe-stringify@2.1.1: {} + + file-uri-to-path@1.0.0: {} + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + joycon@3.1.1: {} + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.8.5 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + napi-build-utils@2.0.0: {} + + negotiator@1.0.0: {} + + node-abi@3.94.0: + dependencies: + semver: 7.8.5 + + object-inspect@1.13.4: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-to-regexp@8.4.2: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.4 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.94.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.5 + tunnel-agent: 0.6.0 + + process-warning@5.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + real-require@0.2.0: {} + + real-require@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + secure-json-parse@4.1.0: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + split2@4.2.0: {} + + statuses@2.0.2: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + strip-json-comments@5.0.3: {} + + tar-fs@2.1.5: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + + toidentifier@1.0.1: {} + + tsx@4.22.4: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@6.0.3: {} + + undici-types@8.3.0: {} + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + xmlchars@2.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..96a2473 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - "UndauntedMetagame" + - "UndauntedDeployServer" +allowBuilds: + better-sqlite3: true + esbuild: true