diff --git a/Meyton Message Protocol.md b/Meyton Message Protocol.md new file mode 100644 index 00000000..161509da --- /dev/null +++ b/Meyton Message Protocol.md @@ -0,0 +1,12 @@ +# Meyton Message Protocol + +Target IP: 224.0.0.3 UDP, Port:49497 + +| Start Address | Type | Size (Bytes) | Description | +| ------------- | -------- | -------------- | ----------------------------------------------------------------------------------------------------------- | +| 0x00 | bytes | 36 | 00 00 03 01 06 08 53 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 51 00 00 00 00 00 00 00 01 00 00 00 | +| 0x24 | uint32 | 4 | Message ID, 0x35 for Message, 0xFB is for a simulated button press | +| 0x28 | Bitfield | 52 / 53 | Target RangeNo (1-416), lsb of 0x28 gets ignored | +| 0x5D | Unknown | 79 | Padding? Always 0x00 | +| 0xAC | uint32 | 4 | Message Length | +| 0xB0 | char[] | Message Length | ASCII (HTML) Message Content | diff --git a/database/local/docker-compose.yaml b/database/local/docker-compose.yaml index 24ae53c7..f99b340f 100644 --- a/database/local/docker-compose.yaml +++ b/database/local/docker-compose.yaml @@ -19,7 +19,7 @@ services: timeout: 5s retries: 5 volumes: - - postgres_data:/var/lib/postgresql/data + - postgres_data:/var/lib/postgresql dc-db-migrate: container_name: dc-db-migrate image: ghcr.io/svenfinn/dc-db-migrate:${APP_VERSION} diff --git a/database/local/migrations/20241010070636_add_known_ranges/migration.sql b/database/local/migrations/20241010070636_add_known_ranges/migration.sql index adbb6ee6..69b00181 100644 --- a/database/local/migrations/20241010070636_add_known_ranges/migration.sql +++ b/database/local/migrations/20241010070636_add_known_ranges/migration.sql @@ -11,6 +11,3 @@ CREATE TABLE "public"."KnownRanges" ( -- CreateIndex CREATE UNIQUE INDEX "KnownRanges_rangeId_key" ON "public"."KnownRanges"("rangeId"); - --- CreateIndex -CREATE UNIQUE INDEX "KnownRanges_lastIp_key" ON "public"."KnownRanges"("lastIp"); diff --git a/database/local/package-lock.json b/database/local/package-lock.json index d734a56d..7aec66b9 100644 --- a/database/local/package-lock.json +++ b/database/local/package-lock.json @@ -9,15 +9,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -63,7 +63,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -116,23 +117,83 @@ } }, "node_modules/@prisma/adapter-pg": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.3.0.tgz", - "integrity": "sha512-iuYQMbIPO6i9O45Fv8TB7vWu00BXhCaNAShenqF7gLExGDbnGp5BfFB4yz1K59zQ59jF6tQ9YHrg0P6/J3OoLg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.5.0.tgz", + "integrity": "sha512-EJx7OLULahcC3IjJgdx2qRDNCT+ToY2v66UkeETMCLhNOTgqVzRzYvOEphY7Zp0eHyzfkC33Edd/qqeadf9R4A==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.3.0", + "@prisma/driver-adapter-utils": "7.5.0", + "@types/pg": "8.11.11", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, + "node_modules/@prisma/adapter-pg/node_modules/@types/pg": { + "version": "8.11.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", + "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/pg-types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", + "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/@prisma/client": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.3.0.tgz", - "integrity": "sha512-FXBIxirqQfdC6b6HnNgxGmU7ydCPEPk7maHMOduJJfnTP+MuOGa15X4omjR/zpPUUpm8ef/mEFQjJudOGkXFcQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.5.0.tgz", + "integrity": "sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.3.0" + "@prisma/client-runtime-utils": "7.5.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -151,15 +212,15 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.3.0.tgz", - "integrity": "sha512-dG/ceD9c+tnXATPk8G+USxxYM9E6UdMTnQeQ+1SZUDxTz7SgQcfxEqafqIQHcjdlcNK/pvmmLfSwAs3s2gYwUw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.5.0.tgz", + "integrity": "sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz", - "integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz", + "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -170,9 +231,9 @@ } }, "node_modules/@prisma/debug": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz", - "integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz", + "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -202,65 +263,65 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.3.0.tgz", - "integrity": "sha512-Wdlezh1ck0Rq2dDINkfSkwbR53q53//Eo1vVqVLwtiZ0I6fuWDGNPxwq+SNAIHnsU+FD/m3aIJKevH3vF13U3w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.5.0.tgz", + "integrity": "sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/engines": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz", - "integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz", + "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/fetch-engine": "7.3.0", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/fetch-engine": "7.5.0", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz", - "integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==", + "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz", + "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz", - "integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz", + "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/get-platform": { @@ -288,11 +349,15 @@ "license": "Apache-2.0" }, "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz", + "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==", "devOptional": true, "license": "Apache-2.0", + "engines": { + "node": "^20.19 || ^22.12 || ^24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", @@ -307,19 +372,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", - "dev": true, + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -329,9 +393,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "peer": true, @@ -433,9 +497,9 @@ } }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "devOptional": true, "license": "MIT" }, @@ -469,8 +533,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deepmerge-ts": { "version": "7.1.5", @@ -507,9 +570,9 @@ "license": "MIT" }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -647,6 +710,7 @@ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -781,9 +845,9 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", - "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -799,12 +863,18 @@ } }, "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", "devOptional": true, "license": "MIT" }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -837,14 +907,15 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", - "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", + "peer": true, "dependencies": { - "pg-connection-string": "^2.10.1", - "pg-pool": "^3.11.0", - "pg-protocol": "^1.11.0", + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -871,9 +942,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", - "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", "license": "MIT" }, "node_modules/pg-int8": { @@ -885,19 +956,28 @@ "node": ">=4.0.0" } }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/pg-pool": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", - "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", - "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", "license": "MIT" }, "node_modules/pg-types": { @@ -999,18 +1079,25 @@ "node": ">=0.10.0" } }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, "node_modules/prisma": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz", - "integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz", + "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/config": "7.3.0", + "@prisma/config": "7.5.0", "@prisma/dev": "0.20.0", - "@prisma/engines": "7.3.0", - "@prisma/studio-core": "0.13.1", + "@prisma/engines": "7.5.0", + "@prisma/studio-core": "0.21.1", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -1081,9 +1168,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1092,9 +1179,9 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1102,7 +1189,7 @@ "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/readdirp": { @@ -1158,8 +1245,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/seq-queue": { "version": "0.0.5", @@ -1230,9 +1316,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "devOptional": true, "license": "MIT", "engines": { @@ -1245,6 +1331,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1254,10 +1341,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/valibot": { diff --git a/database/local/package.json b/database/local/package.json index df92436b..1698e95c 100644 --- a/database/local/package.json +++ b/database/local/package.json @@ -13,15 +13,15 @@ "license": "ISC", "description": "", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" }, "files": [ diff --git a/database/local/pgadmin/docker-compose.yaml b/database/local/pgadmin/docker-compose.yaml index dee0a474..d862da82 100644 --- a/database/local/pgadmin/docker-compose.yaml +++ b/database/local/pgadmin/docker-compose.yaml @@ -23,5 +23,3 @@ services: depends_on: dc-database: condition: service_healthy - dc-db-migrate: - condition: service_completed_successfully diff --git a/database/local/schema.prisma b/database/local/schema.prisma index 38e7b3a6..a2dd8cf6 100644 --- a/database/local/schema.prisma +++ b/database/local/schema.prisma @@ -58,7 +58,7 @@ enum CacheTypes { model KnownRanges { macAddress String @id rangeId Int? @unique - lastIp String? @unique @db.Inet + lastIp String? @db.Inet createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt } diff --git a/database/smdb/package-lock.json b/database/smdb/package-lock.json index 602db7d3..d8ac433a 100644 --- a/database/smdb/package-lock.json +++ b/database/smdb/package-lock.json @@ -8,14 +8,14 @@ "name": "dc-db-smdb", "version": "1.0.0", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -24,15 +24,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -78,7 +78,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -131,22 +132,22 @@ } }, "node_modules/@prisma/adapter-mariadb": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.3.0.tgz", - "integrity": "sha512-cZaNZqdnm255Di8+0ztWDVdg40zRburNEMqHN2AIP98SO0Xbo9UDqHKC7sYkmm5Rqy9fNVxMjBJnoiZJ4Ae+tw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.5.0.tgz", + "integrity": "sha512-p2fP31AXkuB8sBvX/c3lB3a6BVGmTnCpfltzNa8uXxoGkV/qhCYtu7ZNfxQO7JbPtAoGNK5A6/h6JC5FWQxUkQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.3.0", + "@prisma/driver-adapter-utils": "7.5.0", "mariadb": "3.4.5" } }, "node_modules/@prisma/client": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.3.0.tgz", - "integrity": "sha512-FXBIxirqQfdC6b6HnNgxGmU7ydCPEPk7maHMOduJJfnTP+MuOGa15X4omjR/zpPUUpm8ef/mEFQjJudOGkXFcQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.5.0.tgz", + "integrity": "sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.3.0" + "@prisma/client-runtime-utils": "7.5.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -165,15 +166,15 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.3.0.tgz", - "integrity": "sha512-dG/ceD9c+tnXATPk8G+USxxYM9E6UdMTnQeQ+1SZUDxTz7SgQcfxEqafqIQHcjdlcNK/pvmmLfSwAs3s2gYwUw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.5.0.tgz", + "integrity": "sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz", - "integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz", + "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -184,9 +185,9 @@ } }, "node_modules/@prisma/debug": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz", - "integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz", + "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -216,65 +217,65 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.3.0.tgz", - "integrity": "sha512-Wdlezh1ck0Rq2dDINkfSkwbR53q53//Eo1vVqVLwtiZ0I6fuWDGNPxwq+SNAIHnsU+FD/m3aIJKevH3vF13U3w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.5.0.tgz", + "integrity": "sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/engines": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz", - "integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz", + "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/fetch-engine": "7.3.0", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/fetch-engine": "7.5.0", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz", - "integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==", + "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz", + "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz", - "integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz", + "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/get-platform": { @@ -302,11 +303,15 @@ "license": "Apache-2.0" }, "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz", + "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==", "devOptional": true, "license": "Apache-2.0", + "engines": { + "node": "^20.19 || ^22.12 || ^24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", @@ -327,19 +332,26 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "peer": true, @@ -441,9 +453,9 @@ } }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "devOptional": true, "license": "MIT" }, @@ -477,8 +489,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dc-db-local": { "resolved": "../local", @@ -518,9 +529,9 @@ "license": "MIT" }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -658,6 +669,7 @@ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -835,9 +847,9 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", - "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -853,9 +865,9 @@ } }, "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", "devOptional": true, "license": "MIT" }, @@ -917,17 +929,18 @@ } }, "node_modules/prisma": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz", - "integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz", + "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/config": "7.3.0", + "@prisma/config": "7.5.0", "@prisma/dev": "0.20.0", - "@prisma/engines": "7.3.0", - "@prisma/studio-core": "0.13.1", + "@prisma/engines": "7.5.0", + "@prisma/studio-core": "0.21.1", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -998,9 +1011,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1009,9 +1022,9 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1019,7 +1032,7 @@ "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/readdirp": { @@ -1074,8 +1087,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/seq-queue": { "version": "0.0.5", @@ -1137,9 +1149,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "devOptional": true, "license": "MIT", "engines": { @@ -1152,6 +1164,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/database/smdb/package.json b/database/smdb/package.json index 89d68eee..0900d1c5 100644 --- a/database/smdb/package.json +++ b/database/smdb/package.json @@ -10,15 +10,15 @@ "generate": "npx prisma generate --no-hints" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" }, "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "files": [ "dist", diff --git a/database/smdb/schema.prisma b/database/smdb/schema.prisma index 9a3d7693..3ba96999 100644 --- a/database/smdb/schema.prisma +++ b/database/smdb/schema.prisma @@ -660,11 +660,11 @@ model Shooter { image Bytes? @map("SchuetzenFoto") @db.MediumBlob shooterFilters FilterShooter[] - club Club @relation(fields: [clubId], references: [id], map: "Schuetze_ibfk_1") - nation Country @relation(fields: [nationShort], references: [short], map: "Schuetze_ibfk_2") + club Club @relation(fields: [clubId], references: [id], map: "Schuetze_ibfk_1") + nation Country @relation(fields: [nationShort], references: [short], map: "Schuetze_ibfk_2") seedingLists SeedingList[] startListEntry StartListEntry[] - clubShooters Vereinszuordnung[] + clubShooters ClubAssoc[] @@index([nationShort], map: "LandesKuerzel") @@index([lastName], map: "Nachname") @@ -804,14 +804,14 @@ model Club { teams Team[] shooters Shooter[] startListEntries StartListEntry[] - union Union @relation(fields: [unionId], references: [id], map: "Verein_ibfk_1") - clubShooter Vereinszuordnung[] + union Union @relation(fields: [unionId], references: [id], map: "Verein_ibfk_1") + clubShooter ClubAssoc[] @@index([unionId], map: "VerbandsID") @@map("Verein") } -model Vereinszuordnung { +model ClubAssoc { shooterId BigInt @map("SportpassID") clubId Int @map("VereinsID") @@ -823,11 +823,8 @@ model Vereinszuordnung { @@map("Vereinszuordnung") } -// Contains version numbers of the database model Version { - Versionsnummer Int - - @@ignore + version Int @unique @map("Versionsnummer") @db.Int } model Zeit { diff --git a/database/ssmdb2/package-lock.json b/database/ssmdb2/package-lock.json index ff6e7004..36f511fb 100644 --- a/database/ssmdb2/package-lock.json +++ b/database/ssmdb2/package-lock.json @@ -9,14 +9,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -25,15 +25,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -79,7 +79,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -132,22 +133,22 @@ } }, "node_modules/@prisma/adapter-mariadb": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.3.0.tgz", - "integrity": "sha512-cZaNZqdnm255Di8+0ztWDVdg40zRburNEMqHN2AIP98SO0Xbo9UDqHKC7sYkmm5Rqy9fNVxMjBJnoiZJ4Ae+tw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.5.0.tgz", + "integrity": "sha512-p2fP31AXkuB8sBvX/c3lB3a6BVGmTnCpfltzNa8uXxoGkV/qhCYtu7ZNfxQO7JbPtAoGNK5A6/h6JC5FWQxUkQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.3.0", + "@prisma/driver-adapter-utils": "7.5.0", "mariadb": "3.4.5" } }, "node_modules/@prisma/client": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.3.0.tgz", - "integrity": "sha512-FXBIxirqQfdC6b6HnNgxGmU7ydCPEPk7maHMOduJJfnTP+MuOGa15X4omjR/zpPUUpm8ef/mEFQjJudOGkXFcQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.5.0.tgz", + "integrity": "sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.3.0" + "@prisma/client-runtime-utils": "7.5.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -166,15 +167,15 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.3.0.tgz", - "integrity": "sha512-dG/ceD9c+tnXATPk8G+USxxYM9E6UdMTnQeQ+1SZUDxTz7SgQcfxEqafqIQHcjdlcNK/pvmmLfSwAs3s2gYwUw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.5.0.tgz", + "integrity": "sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz", - "integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz", + "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -185,9 +186,9 @@ } }, "node_modules/@prisma/debug": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz", - "integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz", + "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==", "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -217,65 +218,65 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.3.0.tgz", - "integrity": "sha512-Wdlezh1ck0Rq2dDINkfSkwbR53q53//Eo1vVqVLwtiZ0I6fuWDGNPxwq+SNAIHnsU+FD/m3aIJKevH3vF13U3w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.5.0.tgz", + "integrity": "sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/engines": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz", - "integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz", + "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/fetch-engine": "7.3.0", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/fetch-engine": "7.5.0", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz", - "integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==", + "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz", + "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz", - "integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz", + "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/get-platform": { @@ -303,11 +304,15 @@ "license": "Apache-2.0" }, "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz", + "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==", "devOptional": true, "license": "Apache-2.0", + "engines": { + "node": "^20.19 || ^22.12 || ^24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", @@ -328,19 +333,26 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "peer": true, @@ -442,9 +454,9 @@ } }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "devOptional": true, "license": "MIT" }, @@ -478,8 +490,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dc-db-local": { "resolved": "../local", @@ -519,9 +530,9 @@ "license": "MIT" }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -659,6 +670,7 @@ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -836,9 +848,9 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", - "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -854,9 +866,9 @@ } }, "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", "devOptional": true, "license": "MIT" }, @@ -918,17 +930,18 @@ } }, "node_modules/prisma": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz", - "integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz", + "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/config": "7.3.0", + "@prisma/config": "7.5.0", "@prisma/dev": "0.20.0", - "@prisma/engines": "7.3.0", - "@prisma/studio-core": "0.13.1", + "@prisma/engines": "7.5.0", + "@prisma/studio-core": "0.21.1", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -999,9 +1012,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1010,9 +1023,9 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -1020,7 +1033,7 @@ "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/readdirp": { @@ -1075,8 +1088,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/seq-queue": { "version": "0.0.5", @@ -1138,9 +1150,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "devOptional": true, "license": "MIT", "engines": { @@ -1153,6 +1165,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/database/ssmdb2/package.json b/database/ssmdb2/package.json index b07a92b5..0816bc02 100644 --- a/database/ssmdb2/package.json +++ b/database/ssmdb2/package.json @@ -13,14 +13,14 @@ "license": "ISC", "description": "", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" }, "files": [ diff --git a/database/tableWatcher/package-lock.json b/database/tableWatcher/package-lock.json index ac039033..fbe528c3 100644 --- a/database/tableWatcher/package-lock.json +++ b/database/tableWatcher/package-lock.json @@ -13,8 +13,8 @@ "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -23,12 +23,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -320,7 +320,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.0.20", @@ -373,9 +374,9 @@ } }, "node_modules/@prisma/config": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz", - "integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz", + "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -386,9 +387,9 @@ } }, "node_modules/@prisma/debug": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz", - "integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz", + "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==", "dev": true, "license": "Apache-2.0" }, @@ -419,56 +420,56 @@ } }, "node_modules/@prisma/engines": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz", - "integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz", + "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/fetch-engine": "7.3.0", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/fetch-engine": "7.5.0", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/engines-version": { - "version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz", - "integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==", + "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz", + "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz", - "integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz", + "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0", - "@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735", - "@prisma/get-platform": "7.3.0" + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/get-platform": "7.5.0" } }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz", - "integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.3.0" + "@prisma/debug": "7.5.0" } }, "node_modules/@prisma/get-platform": { @@ -496,11 +497,15 @@ "license": "Apache-2.0" }, "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz", + "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==", "dev": true, "license": "Apache-2.0", + "engines": { + "node": "^20.19 || ^22.12 || ^24.0", + "pnpm": "8" + }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", @@ -519,19 +524,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", "peer": true, @@ -620,9 +625,9 @@ } }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "dev": true, "license": "MIT" }, @@ -656,8 +661,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dc-logger": { "resolved": "../../logger", @@ -872,6 +876,7 @@ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -1010,9 +1015,9 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz", - "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1028,9 +1033,9 @@ } }, "node_modules/nypm/node_modules/citty": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", - "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", "dev": true, "license": "MIT" }, @@ -1092,17 +1097,17 @@ } }, "node_modules/prisma": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz", - "integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz", + "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.3.0", + "@prisma/config": "7.5.0", "@prisma/dev": "0.20.0", - "@prisma/engines": "7.3.0", - "@prisma/studio-core": "0.13.1", + "@prisma/engines": "7.5.0", + "@prisma/studio-core": "0.21.1", "mysql2": "3.15.3", "postgres": "3.4.7" }, @@ -1173,9 +1178,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "dev": true, "license": "MIT", "peer": true, @@ -1184,9 +1189,9 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "dev": true, "license": "MIT", "peer": true, @@ -1194,7 +1199,7 @@ "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/readdirp": { @@ -1250,8 +1255,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/seq-queue": { "version": "0.0.5", @@ -1356,9 +1360,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { @@ -1369,6 +1373,7 @@ "version": "5.9.3", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1378,7 +1383,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/database/tableWatcher/package.json b/database/tableWatcher/package.json index 0911294e..485a1621 100644 --- a/database/tableWatcher/package.json +++ b/database/tableWatcher/package.json @@ -12,8 +12,8 @@ "license": "ISC", "description": "", "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" }, "files": [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index de8926f4..ae24d3ac 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,20 +11,20 @@ "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@reduxjs/toolkit": "^2.11.2", - "@rjsf/core": "^6.2.5", - "@rjsf/react-bootstrap": "^6.2.5", - "@rjsf/utils": "^6.2.5", - "@rjsf/validator-ajv8": "^6.2.5", + "@rjsf/core": "^6.4.1", + "@rjsf/react-bootstrap": "^6.4.1", + "@rjsf/utils": "^6.4.1", + "@rjsf/validator-ajv8": "^6.4.1", "bootstrap": "^5.3.8", "dc-ranges-types": "file:../ranges/types", "dc-screens-types": "file:../screens/types", "mime": "^4.1.0", - "next": "^16.1.4", + "next": "^16.1.6", "raw-loader": "^4.0.2", - "react": "^19.2.3", + "react": "^19.2.4", "react-bootstrap": "^2.10.10", - "react-dom": "^19.2.3", - "react-icons": "^5.5.0", + "react-dom": "^19.2.4", + "react-icons": "^5.6.0", "react-redux": "^9.2.0", "react-select": "^5.10.2", "react-shadow": "^20.6.0", @@ -32,8 +32,8 @@ "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/react": "^19.2.9", + "@types/node": "^25.5.0", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/smallest-enclosing-circle": "^1.0.3", "typescript": "~5.9.3" @@ -44,7 +44,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -56,7 +56,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -841,7 +841,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -864,15 +863,15 @@ } }, "node_modules/@next/env": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.4.tgz", - "integrity": "sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.4.tgz", - "integrity": "sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -886,9 +885,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.4.tgz", - "integrity": "sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -902,9 +901,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.4.tgz", - "integrity": "sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -918,9 +917,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.4.tgz", - "integrity": "sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -934,9 +933,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.4.tgz", - "integrity": "sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -950,9 +949,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.4.tgz", - "integrity": "sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -966,9 +965,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.4.tgz", - "integrity": "sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -982,9 +981,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.4.tgz", - "integrity": "sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -1002,6 +1001,7 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1112,13 +1112,14 @@ } }, "node_modules/@rjsf/core": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.2.5.tgz", - "integrity": "sha512-k/2aAKj9IY7JBcnPrYv7frgHkfK0KsS7h8PgPW14GJREh+X5EX/icrypcQu5ge/Ggbwi+90plJll07YiRV/lFg==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.4.1.tgz", + "integrity": "sha512-+QaiSgQnOuO6ghIsohH2u/QcylkN+Da2968a75g/i4oARYJRYVxXDm2u3JR5aXndpMb4t4jTFrYyG8cNIv6oEg==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", "markdown-to-jsx": "^8.0.0", "prop-types": "^15.8.1" }, @@ -1126,14 +1127,14 @@ "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^6.2.x", + "@rjsf/utils": "^6.4.x", "react": ">=18" } }, "node_modules/@rjsf/react-bootstrap": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@rjsf/react-bootstrap/-/react-bootstrap-6.2.5.tgz", - "integrity": "sha512-m+ZREcgqwIB5vUsqyay77u9nhMYY6+bYNcwQ9eT6rob4MTy/aGgl6ojK/C8r1DZk5mtmYNxuyvGFL9f56QtAVw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@rjsf/react-bootstrap/-/react-bootstrap-6.4.1.tgz", + "integrity": "sha512-cls64Q6uV+1x0HpEQ0jO4ZG2VAKG3ky0cjQckyIukxgyO6sFxUvz1qdfXZm+5DXXsj05mBAPKxkGtaW2CxmvTA==", "license": "MIT", "dependencies": { "@react-icons/all-files": "^4.1.0" @@ -1142,23 +1143,24 @@ "node": ">=20" }, "peerDependencies": { - "@rjsf/core": "^6.2.x", - "@rjsf/utils": "^6.2.x", + "@rjsf/core": "^6.4.x", + "@rjsf/utils": "^6.4.x", "react": ">=18", "react-bootstrap": "^2.x" } }, "node_modules/@rjsf/utils": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.2.5.tgz", - "integrity": "sha512-29SvRuY3gKyAHUUnIiJiAF/mTnokgrE7XqUXMj+CZK+sGcmAegwhlnQMJgLQciTodMwTwOaDyV1Fxc47VKTHFw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.4.1.tgz", + "integrity": "sha512-5NL3jwt3rIS5/WRTrKt++y40FS/ScKGVwYJ3jIrHSQHSwBdLnd4cHf2zcnA97L1Klj8I6tvS/ugh+blf/Diwuw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@x0k/json-schema-merge": "^1.0.2", "fast-uri": "^3.1.0", "jsonpointer": "^5.0.1", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", "react-is": "^18.3.1" }, "engines": { @@ -1169,21 +1171,21 @@ } }, "node_modules/@rjsf/validator-ajv8": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.2.5.tgz", - "integrity": "sha512-+yLhFRuT2aY91KiUujhUKg8SyTBrUuQP3QAFINeGi+RljA3S+NQN56oeCaNdz9X+35+Sdy6jqmxy/0Q2K+K9vQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.4.1.tgz", + "integrity": "sha512-Gx28sRIV7E4CYs2c7BxOGLX44p5IlJE+IaD7GbVk1S+6TxDATqFBSYYZukLB+/vNk3urpndQMreQLKW3W7POHQ==", "license": "Apache-2.0", "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^2.1.1", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21" + "lodash": "^4.17.23", + "lodash-es": "^4.17.23" }, "engines": { "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^6.2.x" + "@rjsf/utils": "^6.4.x" } }, "node_modules/@socket.io/component-emitter": { @@ -1218,7 +1220,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -1229,7 +1230,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -1239,8 +1239,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1249,12 +1248,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/parse-json": { @@ -1270,10 +1269,11 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1321,7 +1321,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -1331,29 +1330,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1364,15 +1359,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1385,7 +1378,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -1395,7 +1387,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -1404,15 +1395,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1429,7 +1418,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -1443,7 +1431,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1456,7 +1443,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1471,7 +1457,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -1490,15 +1475,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/acorn": { "version": "8.15.0", @@ -1518,7 +1501,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -1531,6 +1513,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1564,7 +1547,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -1661,8 +1643,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", @@ -1698,7 +1679,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -1719,8 +1699,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -1808,8 +1787,7 @@ "version": "1.5.194", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz", "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -1864,7 +1842,6 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -1886,15 +1863,13 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -1916,7 +1891,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -1930,7 +1904,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -1943,7 +1916,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -1953,7 +1925,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -1963,7 +1934,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -2015,22 +1985,19 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -2129,7 +2096,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -2201,7 +2167,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" } @@ -2221,15 +2186,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/loose-envify": { @@ -2271,8 +2236,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/mime": { "version": "4.1.0", @@ -2294,7 +2258,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2304,7 +2267,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -2322,16 +2284,15 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.4.tgz", - "integrity": "sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "16.1.4", + "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", @@ -2345,14 +2306,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.4", - "@next/swc-darwin-x64": "16.1.4", - "@next/swc-linux-arm64-gnu": "16.1.4", - "@next/swc-linux-arm64-musl": "16.1.4", - "@next/swc-linux-x64-gnu": "16.1.4", - "@next/swc-linux-x64-musl": "16.1.4", - "@next/swc-win32-arm64-msvc": "16.1.4", - "@next/swc-win32-x64-msvc": "16.1.4", + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { @@ -2382,8 +2343,7 @@ "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", @@ -2541,7 +2501,6 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -2567,10 +2526,11 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2580,6 +2540,7 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.24.7", "@restart/hooks": "^0.4.9", @@ -2607,21 +2568,22 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-icons": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", "license": "MIT", "peerDependencies": { "react": "*" @@ -2644,6 +2606,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -2717,7 +2680,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -2790,8 +2754,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/scheduler": { "version": "0.27.0", @@ -2822,6 +2785,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2866,7 +2830,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -2990,7 +2953,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3001,7 +2963,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3040,7 +3001,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3068,7 +3028,6 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -3078,7 +3037,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", @@ -3097,7 +3055,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -3132,7 +3089,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -3183,9 +3139,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -3207,7 +3163,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -3265,7 +3220,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -3279,7 +3233,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz", "integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -3328,7 +3281,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -3338,7 +3290,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/frontend/package.json b/frontend/package.json index 2896afa6..22c12348 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,20 +12,20 @@ "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", "@reduxjs/toolkit": "^2.11.2", - "@rjsf/core": "^6.2.5", - "@rjsf/react-bootstrap": "^6.2.5", - "@rjsf/utils": "^6.2.5", - "@rjsf/validator-ajv8": "^6.2.5", + "@rjsf/core": "^6.4.1", + "@rjsf/react-bootstrap": "^6.4.1", + "@rjsf/utils": "^6.4.1", + "@rjsf/validator-ajv8": "^6.4.1", "bootstrap": "^5.3.8", "dc-ranges-types": "file:../ranges/types", "dc-screens-types": "file:../screens/types", "mime": "^4.1.0", - "next": "^16.1.4", + "next": "^16.1.6", "raw-loader": "^4.0.2", - "react": "^19.2.3", + "react": "^19.2.4", "react-bootstrap": "^2.10.10", - "react-dom": "^19.2.3", - "react-icons": "^5.5.0", + "react-dom": "^19.2.4", + "react-icons": "^5.6.0", "react-redux": "^9.2.0", "react-select": "^5.10.2", "react-shadow": "^20.6.0", @@ -33,8 +33,8 @@ "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/react": "^19.2.9", + "@types/node": "^25.5.0", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/smallest-enclosing-circle": "^1.0.3", "typescript": "~5.9.3" diff --git a/frontend/src/app/show/components/Ranges/Draw/hits/index.tsx b/frontend/src/app/show/components/Ranges/Draw/hits/index.tsx index 303478e1..4b7f7412 100644 --- a/frontend/src/app/show/components/Ranges/Draw/hits/index.tsx +++ b/frontend/src/app/show/components/Ranges/Draw/hits/index.tsx @@ -6,19 +6,29 @@ import { compareJSON } from ".."; export const DrawHits = memo( ({ round, hits, gauge, strokeWidth }: { round: Round | null, hits: Hits, gauge: UnsignedNumber, strokeWidth: number }): React.JSX.Element => { - if (round?.mode.mode == "fullHidden") return <>; - let startingIndex = 0; - if (round && hits.length < round.maxHits) { - const hitsPerView = round?.hitsPerView - startingIndex = Math.floor((hits.length - 1) / hitsPerView) * hitsPerView; + if (!round) return <> + if (round.mode.mode == "fullHidden") return <>; + + let selectedHits = hits; + const maxHitId = Math.max(...hits.map(h => h.id)); + + if (maxHitId < round.maxHits) { + const latestSeriesId = + Math.floor((maxHitId - 1) / round.hitsPerView); + + selectedHits = hits.filter( + hit => + Math.floor((hit.id - 1) / round.hitsPerView) === + latestSeriesId + ); } - const hitsCopy = hits.slice(startingIndex) + return ( - {hitsCopy.map((hit, index) => ( - + {selectedHits.map((hit, index) => ( + ))} - {round?.mode.mode == "circle" ? : <>} + {round.mode.mode == "circle" ? : <>} ) }, compareJSON); \ No newline at end of file diff --git a/frontend/src/app/show/components/Ranges/Draw/layout/index.tsx b/frontend/src/app/show/components/Ranges/Draw/layout/index.tsx index 7090912b..fda25f83 100644 --- a/frontend/src/app/show/components/Ranges/Draw/layout/index.tsx +++ b/frontend/src/app/show/components/Ranges/Draw/layout/index.tsx @@ -67,18 +67,26 @@ function getSizeInternal(round: Round, gauge: UnsignedNumber, hits: Hits): [numb export function getSizeAuto(gauge: UnsignedNumber, round: Round | null, hits: Hits): [number, number] | null { - let startingIndex = 0; - if (round && hits.length < round.maxHits) { - const hitsPerView = round.hitsPerView; - startingIndex = Math.floor((hits.length - 1) / hitsPerView) * hitsPerView; + let selectedHits = hits; + const maxHitId = Math.max(...hits.map(h => h.id)); + if (round && maxHitId < round.maxHits) { + const latestSeriesId = + Math.floor((maxHitId - 1) / round.hitsPerView); + + selectedHits = hits.filter( + hit => + Math.floor((hit.id - 1) / round.hitsPerView) === + latestSeriesId + ); } - const hitsCopy = hits.slice(startingIndex).filter(hit => hit.valid); - if (hitsCopy.length === 0 || round?.mode.mode == "fullHidden") { + const validHits = selectedHits.filter((hit) => hit.valid) as Array; + if (validHits.length === 0 || round?.mode.mode == "fullHidden") { return null; } - const sizes = [Math.max(...hitsCopy.map(hit => Math.abs(hit.x))), - Math.max(...hitsCopy.map(hit => Math.abs(hit.y)))]; + + const sizes = [Math.max(...validHits.map(hit => Math.abs(hit.x))), + Math.max(...validHits.map(hit => Math.abs(hit.y)))]; return sizes.map((s) => s * 2 + gauge) as [number, number]; } diff --git a/frontend/src/app/show/components/Ranges/Grid/grid.module.css b/frontend/src/app/show/components/Ranges/Grid/grid.module.css index ce81c8fd..d3879f71 100644 --- a/frontend/src/app/show/components/Ranges/Grid/grid.module.css +++ b/frontend/src/app/show/components/Ranges/Grid/grid.module.css @@ -2,19 +2,30 @@ div.grid { width: 100%; height: 100%; position: relative; - --border-width: calc(var(--height) *0.005); + + --border-width: calc(var(--height) * 0.005); + display: grid; - padding-bottom: var(--border-width); - padding-right: var(--border-width); + grid-template-rows: repeat(var(--rows, 1), 1fr); + grid-template-columns: repeat(var(--columns, 1), 1fr); + + font-size: calc(calc(var(--height, 0px) * var(--font-height-ratio)) / var(--rows, 1)); + + border: var(--border-width) solid #888; + gap: var(--border-width); + background: #888; } + div.grid>div { - border: var(--border-width) solid transparent; - height: calc(100% + var(--border-width)); - width: calc(100% + var(--border-width)); + height: 100%; + width: 100%; position: relative; + + background: white; } -div.grid>div:not(:empty) { - border-color: #888; +div.grid>div:empty { + padding-top: var(--border-width); + background: white; } \ No newline at end of file diff --git a/frontend/src/app/show/components/Ranges/Grid/index.tsx b/frontend/src/app/show/components/Ranges/Grid/index.tsx index 8aedf6f7..296ae446 100644 --- a/frontend/src/app/show/components/Ranges/Grid/index.tsx +++ b/frontend/src/app/show/components/Ranges/Grid/index.tsx @@ -13,10 +13,10 @@ export default function Grid({ rows, columns, children, className }: GridProps): return (
+ "--rows": rows, + "--columns": columns, + } as React.CSSProperties + } className={styles.grid}> {children}
diff --git a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/hitTotal.tsx b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/hitTotal.tsx index 78a78d3d..476b8ecf 100644 --- a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/hitTotal.tsx +++ b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/hitTotal.tsx @@ -1,9 +1,7 @@ -import { getTotal } from "@frontend/app/show/lib/ranges"; +import { getTotal } from "@frontend/app/show/lib/ranges/accumulate"; import { useAppSelector } from "../../ranges-store/store"; import styles from "./infoPanel.module.css"; - - export function HitTotal({ id }: { id: number }): React.JSX.Element { const total = useAppSelector((state) => { const range = state.ranges[id]; diff --git a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/seriesTable.tsx b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/seriesTable.tsx index 47e70e1d..23311558 100644 --- a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/seriesTable.tsx +++ b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/seriesTable.tsx @@ -1,4 +1,4 @@ -import { getSeries } from "@frontend/app/show/lib/ranges"; +import { getSeries } from "@frontend/app/show/lib/ranges/accumulate"; import { useAppSelector } from "../../ranges-store/store"; import styles from "./infoPanel.module.css"; diff --git a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/shotTable.tsx b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/shotTable.tsx index 4094503b..a39beb09 100644 --- a/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/shotTable.tsx +++ b/frontend/src/app/show/cpcView/components/CpcView/range/infoPanel/shotTable.tsx @@ -1,16 +1,24 @@ -import { Mode, Range } from "dc-ranges-types"; +import { Mode } from "dc-ranges-types"; import { useAppSelector } from "../../ranges-store/store"; import styles from "./infoPanel.module.css"; -import { getHitEval, HitEval } from "@frontend/app/show/lib/ranges"; import ShotArrow from "@frontend/app/show/components/Ranges/Arrow"; import React from "react"; +import { getHitsOfSeries, getLatestSeriesId } from "@frontend/app/show/lib/ranges"; +import { getHitEval } from "@frontend/app/show/lib/ranges/eval"; +import { HitEval } from "@frontend/app/show/lib/ranges/types"; export default function ShotTable({ id }: { id: number }): React.JSX.Element { const hits = useAppSelector(state => { const range = state.ranges[id]; - if (!range || !range.active) return []; - return range.hits[range.round] || []; + if (!range || !range.active || !range.discipline) return []; + const round = range.discipline.rounds[range.round]; + if (!round) return []; + const hits = range.hits[range.round] || []; + + const selectedHits = getHitsOfSeries(round, hits, getLatestSeriesId(round, hits)); + + return selectedHits.reverse(); }) const round = useAppSelector(state => { @@ -21,9 +29,6 @@ export default function ShotTable({ id }: { id: number }): React.JSX.Element { if (!round) return <>; - const currentSumCount = hits.length % round.hitsPerSum || round.hitsPerSum; - const selectedHits = currentSumCount > 10 ? hits.slice(-10) : hits.slice(-currentSumCount); - return (
@@ -36,7 +41,7 @@ export default function ShotTable({ id }: { id: number }): React.JSX.Element { }
- {selectedHits.reverse().map((hit, index) => { + {hits.map((hit, index) => { if (!hit.valid) { return (
diff --git a/frontend/src/app/show/cpcView/components/CpcView/range/rangeView/index.tsx b/frontend/src/app/show/cpcView/components/CpcView/range/rangeView/index.tsx index 74f280ee..5e273e44 100644 --- a/frontend/src/app/show/cpcView/components/CpcView/range/rangeView/index.tsx +++ b/frontend/src/app/show/cpcView/components/CpcView/range/rangeView/index.tsx @@ -7,9 +7,13 @@ export function RangeView({ id }: { id: number }): React.JSX.Element { const range = useAppSelector(state => state.ranges[id]); if (!range) return <>; if (!range.active) return <>; - let shooterName = "- - - Frei - - -"; + let shooterName = "Nicht identifizierbar"; if (range.shooter) { - shooterName = `${range.shooter.lastName}, ${range.shooter.firstName}`; + if (range.shooter.type === "free") { + shooterName = "- - - Frei - - -"; + } else { + shooterName = `${range.shooter.lastName}, ${range.shooter.firstName}`; + } } return ( diff --git a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/currentHit.tsx b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/currentHit.tsx index 74518d9f..7906cb99 100644 --- a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/currentHit.tsx +++ b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/currentHit.tsx @@ -1,9 +1,9 @@ -import { getHitEval, getRoundName } from "../../../../../lib/ranges"; import styles from "./range.module.css" import ShotArrowW from "./ShotArrow"; -import { Hit, Range, Round } from "dc-ranges-types"; +import { Hit, Round } from "dc-ranges-types"; import { useAppSelector } from "../../ranges-store/store"; import { ScaleText } from "@frontend/app/components/base/ScaleText"; +import { getHitEval } from "@frontend/app/show/lib/ranges/eval"; interface CurrentHitProps { id: number diff --git a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/index.tsx b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/index.tsx index 14e820f8..3eb8c4df 100644 --- a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/index.tsx +++ b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/index.tsx @@ -37,7 +37,7 @@ export default function Range({ highlightAssign, id }: DrawTargetRangeProps): Re - +
); } diff --git a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/name.tsx b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/name.tsx index a9776cfc..12ad725e 100644 --- a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/name.tsx +++ b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/name.tsx @@ -6,9 +6,12 @@ export default function Name({ id }: { id: number }): React.JSX.Element { const shooterName = useAppSelector((state) => { const range = state.ranges[id]; if (range && range.active && range.shooter) { + if (range.shooter.type === "free") { + return "- - - Frei - - -"; + } return `${range.shooter.firstName} ${range.shooter.lastName}`; } - return "- - - Frei - - -"; + return "Nicht identifizierbar"; }); return (
diff --git a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/seriesList.tsx b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/seriesList.tsx index 0fccbfec..07006c01 100644 --- a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/seriesList.tsx +++ b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/seriesList.tsx @@ -1,8 +1,7 @@ -import { getSeries } from "../../../../../lib/ranges"; import styles from "./range.module.css" import { useAppSelector } from "../../ranges-store/store"; import { ScaleText } from "@frontend/app/components/base/ScaleText"; -import { current } from "@reduxjs/toolkit"; +import { getSeries } from "@frontend/app/show/lib/ranges/accumulate"; export default function SeriesList({ id }: { id: number }): React.JSX.Element { // This only needs to rerender if the hits change, so we can use a simple selector diff --git a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/total.tsx b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/total.tsx index 8c02c2b7..00f63d3d 100644 --- a/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/total.tsx +++ b/frontend/src/app/show/drawTarget/components/DrawTarget/ranges/range/total.tsx @@ -1,13 +1,13 @@ -import { Range } from "dc-ranges-types"; -import { getTotal } from "../../../../../lib/ranges"; import styles from "./range.module.css" import { useAppSelector } from "../../ranges-store/store"; +import { getExtrapolated, getTotal } from "@frontend/app/show/lib/ranges/accumulate"; -export default function Total({ id }: { id: number }): React.JSX.Element { +export default function Total({ id, extrapolate }: { id: number, extrapolate: boolean }): React.JSX.Element { const total = useAppSelector((state => { const range = state.ranges[id]; if (!range || !range.active || !range.discipline) return null; - return getTotal(range.discipline.rounds[range.round], range.discipline.gauge, range.hits[range.round] || []); + const extrapolated = extrapolate ? ` (${getExtrapolated(range.discipline.rounds[range.round], range.hits[range.round] || [])}` : ""; + return getTotal(range.discipline.rounds[range.round], range.discipline.gauge, range.hits[range.round] || []) + extrapolated; })); if (total === null) return <>; return ( diff --git a/frontend/src/app/show/lib/ranges.ts b/frontend/src/app/show/lib/ranges.ts deleted file mode 100644 index 9726e765..00000000 --- a/frontend/src/app/show/lib/ranges.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { Range, Round, Hit, UnsignedNumber, Hits, ActiveRange, UnsignedInteger, ValidHit } from "dc-ranges-types"; -import { floor, round as mRound } from "./math"; -import smallestEnclosingCircle from "smallest-enclosing-circle"; - -export function getRoundName(data: Range, roundId?: number): string | null { - if (!data.active) return null; - if (!data.discipline) return "Unbekannt"; - if (!roundId) roundId = data.round; - const round = data.discipline.rounds[roundId]; - if (!round) return "Unbekannt"; - return round.name; -} - -type BaseHitEval = { - id: UnsignedInteger - kind: string -} - -type RingsTargetHiddenHitEval = BaseHitEval & { - kind: "rings" - rings: string -} - -type DividerHitEval = BaseHitEval & { - divider: UnsignedNumber - kind: "divider" -} - -type HundredHitEval = BaseHitEval & { - hundred: UnsignedInteger - kind: "hundred" -} - -type CircleHitEval = BaseHitEval & { - x: number, - y: number - kind: "circle" -} - -type HiddenHitEval = BaseHitEval & { - kind: "hidden" -} - -type DecimalHitEval = BaseHitEval & { - decimal: UnsignedInteger - rings: string, - kind: "decimal" -} - -type IntegerTimesDecimalHitEval = BaseHitEval & { - integerTimesDecimal: UnsignedInteger - rings: string, - kind: "integerDecimal" -} - -type RingsDivHitEval = BaseHitEval & { - kind: "ringsDiv", - rings: string, - divider: UnsignedNumber, -}; - -export type HitEval = RingsTargetHiddenHitEval | DividerHitEval | HundredHitEval | CircleHitEval | DecimalHitEval | IntegerTimesDecimalHitEval | RingsDivHitEval | HiddenHitEval; - -function getRingsEval(hit: ValidHit): RingsTargetHiddenHitEval { - return { - kind: "rings", - id: hit.id, - rings: `${floor(hit.rings, 1).toFixed(1)}${hit.innerTen ? '*' : ''}` - }; -} - -function getDividerEval(decimals: UnsignedInteger, hit: ValidHit): DividerHitEval { - return { - kind: "divider", - id: hit.id, - divider: floor(hit.divisor, decimals) - }; -} - - -export function getHitEval(round: Round, hit?: ValidHit): HitEval | null { - if (!hit) return null; - - switch (round.mode.mode) { - case "rings": - case "target": - return getRingsEval(hit); - - case "ringsDiv": - return { - ...getRingsEval(hit), - ...getDividerEval(0, hit), - kind: "ringsDiv", - - }; - - case "divider": - return getDividerEval(round.mode.decimals, hit); - - case "fullHidden": - case "hidden": - if (round.counts == false) { - return getRingsEval(hit); - } else { - return { - kind: "hidden", - id: hit.id, - } - } - - case "hundred": - return { - kind: "hundred", - id: hit.id, - hundred: Math.floor((floor(hit.rings, 1) - 1) * 10 + 1) - } - - case "decimal": - return { - ...getRingsEval(hit), - kind: "decimal", - decimal: Math.floor(hit.rings * 10) % 10 - } - - case "integerDecimal": - const integer = Math.floor(hit.rings); - const decimal = Math.round((hit.rings - integer) * 10); - return { - ...getRingsEval(hit), - kind: "integerDecimal", - integerTimesDecimal: integer * decimal - } - - case "circle": - const precision = 2; - return { - kind: "circle", - id: hit.id, - x: mRound(hit.x, precision), - y: mRound(hit.y, precision) - } - - default: - return null; - } -} - -export function getSeries(round: Round | null, gauge: UnsignedNumber, hits: Hits): Array { - if (!round) return []; - - const series = []; - for (let i = 0; i < hits.length; i += round.hitsPerSum) { - const seriesHits = hits.slice(i, i + round.hitsPerSum); - series.push(accumulateHits(seriesHits, round, gauge, false)); - } - return series; -} - -export function getTotal(round: Round | null, gauge: UnsignedNumber, hits: Hits): string { - if (!round) return "0"; - return accumulateHits(hits, round, gauge, true); -} - -function accumulateHits(hits: Hits, round: Round, gauge: UnsignedNumber, isTotal: boolean): string { - const validHits = hits.filter((hit) => hit.valid); - if (!validHits.length) { - return "0"; - } - switch (round.mode.mode) { - case "circle": - const radius = smallestEnclosingCircle(validHits).r; - return mRound((isNaN(radius) ? 0 : radius * 2) + gauge, 1).toFixed(1); - - case "divider": - const divider = Math.min(...validHits.map((hit) => hit.divisor)); - return `${floor(divider, round.mode.decimals).toFixed(round.mode.decimals)} (${validHits.find((hit) => hit.divisor == divider)?.id})`; - - case "fullHidden": - case "hidden": - if (round.counts) return "***"; - return validHits.reduce((sum, hit) => sum + floor(hit.rings, 0), 0).toFixed(0); - - case "rings": - case "ringsDiv": - const decimals = round.mode.decimals; - return validHits.reduce((sum, hit) => sum + floor(hit.rings, decimals), 0).toFixed(decimals); - - case "hundred": - return validHits.reduce((sum, hit) => sum + (Math.floor(hit.rings) - 1) * 10 + 1, 0).toFixed(0); - - case "decimal": - return validHits.reduce((sum, hit) => sum + Math.floor(hit.rings * 10) % 10, 0).toFixed(0); - - case "integerDecimal": - return validHits.reduce((sum, hit) => { - const integer = Math.floor(hit.rings); - const decimal = Math.floor((hit.rings - integer) * 10); - return sum + integer * decimal; - }, 0).toFixed(0); - - case "target": - const tDecimals = round.mode.decimals; - const exact = round.mode.exact; - const targetValue = round.mode.value; - const sum = validHits.reduce((sum, hit) => { - const value = floor(hit.rings, tDecimals); - if (!exact || sum + value <= targetValue) { - return sum + value; - } else { - return sum; - } - }, 0); - if (isTotal) { - const result = round.mode.value - sum; - if (result < 0) return "0"; - return result.toFixed(round.mode.decimals); - } else { - return sum.toFixed(round.mode.decimals); - } - - default: - return "???"; - } - -} - diff --git a/frontend/src/app/show/lib/ranges/accumulate.ts b/frontend/src/app/show/lib/ranges/accumulate.ts new file mode 100644 index 00000000..d135b8c6 --- /dev/null +++ b/frontend/src/app/show/lib/ranges/accumulate.ts @@ -0,0 +1,143 @@ +import { Hits, Round, UnsignedNumber } from "dc-ranges-types"; +import smallestEnclosingCircle from "smallest-enclosing-circle"; +import { floor, round as mRound } from "../math"; +import { getHitsOfSeries, getLatestSeriesId } from "."; +import { getHitEval } from "./eval"; + +export function getSeries(round: Round | null, gauge: UnsignedNumber, hits: Hits): Array { + if (!round) return []; + if (hits.length == 0) return []; + + const series = []; + const maxSeriesId = getLatestSeriesId(round, hits); + for (let i = 0; i <= maxSeriesId; i++) { + const seriesHits = getHitsOfSeries(round, hits, i); + series.push(accumulateHits(seriesHits, round, gauge, false)); + } + return series; +} + +export function getExtrapolated(round: Round | null, hits: Hits): string | null { + // This does not use the same extrapolation method as the ShootMaster system, + // but it provides a reasonable estimate. + + const MIN_HITS_FOR_EXTRAPOLATION = 8; + + if (!round) return null; + if (!round.counts) return null; + + const mode = round.mode.mode; + if (mode != "rings" && mode != "ringsDiv" && mode != "hundred" && mode != "decimal" && mode != "integerDecimal") return null; + + if (hits.length == 0) return null; + if (hits.length >= round.maxHits) return null; + + const validHits = hits.filter((hit) => hit.valid); + if (validHits.length < MIN_HITS_FOR_EXTRAPOLATION) { + return null; + } + + let hitValues: number[] = []; + let decimals: number = 0; + switch (round.mode.mode) { + case "rings": + case "ringsDiv": + decimals = round.mode.decimals; + hitValues = validHits.map((hit) => floor(hit.rings, decimals)); + break; + case "hundred": + decimals = 0; + hitValues = validHits.map((hit) => Math.floor(hit.rings) * 10); + break; + case "decimal": + decimals = 0; + hitValues = validHits.map((hit) => Math.floor(hit.rings * 10) % 10); + break; + case "integerDecimal": + decimals = 0; + hitValues = validHits.map((hit) => { + const integer = Math.floor(hit.rings); + const decimal = Math.floor((hit.rings - integer) * 10); + return integer * decimal; + }); + break; + default: + return null; + } + + const sum = hitValues.reduce((sum, value) => sum + value, 0); + + const average = sum / hitValues.length; + + const extrapolatedValue = sum + average * (round.maxHits - hits.length); + + return mRound(extrapolatedValue, decimals || 0).toFixed(decimals || 0); +} + +export function getTotal(round: Round | null, gauge: UnsignedNumber, hits: Hits): string { + if (!round) return "0"; + return accumulateHits(hits, round, gauge, true); +} + +function accumulateHits(hits: Hits, round: Round, gauge: UnsignedNumber, isTotal: boolean): string { + const validHits = hits.filter((hit) => hit.valid); + if (!validHits.length) { + return "0"; + } + switch (round.mode.mode) { + case "circle": + const radius = smallestEnclosingCircle(validHits).r; + return mRound((isNaN(radius) ? 0 : radius * 2) + gauge, 1).toFixed(1); + + case "divider": + const divider = Math.min(...validHits.map((hit) => hit.divisor)); + return `${floor(divider, round.mode.decimals).toFixed(round.mode.decimals)} (${validHits.find((hit) => hit.divisor == divider)?.id})`; + + case "fullHidden": + case "hidden": + if (round.counts) return "***"; + return validHits.reduce((sum, hit) => sum + floor(hit.rings, 0), 0).toFixed(0); + + case "rings": + case "ringsDiv": + const decimals = round.mode.decimals; + return validHits.reduce((sum, hit) => sum + floor(hit.rings, decimals), 0).toFixed(decimals); + + case "hundred": + return validHits.reduce((sum, hit) => sum + (Math.floor(hit.rings) - 1) * 10 + 1, 0).toFixed(0); + + case "decimal": + return validHits.reduce((sum, hit) => sum + Math.floor(hit.rings * 10) % 10, 0).toFixed(0); + + case "integerDecimal": + return validHits.reduce((sum, hit) => { + const integer = Math.floor(hit.rings); + const decimal = Math.floor((hit.rings - integer) * 10); + return sum + integer * decimal; + }, 0).toFixed(0); + + case "target": + const tDecimals = round.mode.decimals; + const exact = round.mode.exact; + const targetValue = round.mode.value; + const sum = validHits.reduce((sum, hit) => { + const value = floor(hit.rings, tDecimals); + if (!exact || sum + value <= targetValue) { + return sum + value; + } else { + return sum; + } + }, 0); + if (isTotal) { + const result = round.mode.value - sum; + if (result < 0) return "0"; + return result.toFixed(round.mode.decimals); + } else { + return sum.toFixed(round.mode.decimals); + } + + default: + return "???"; + } + +} \ No newline at end of file diff --git a/frontend/src/app/show/lib/ranges/eval.ts b/frontend/src/app/show/lib/ranges/eval.ts new file mode 100644 index 00000000..02b5864a --- /dev/null +++ b/frontend/src/app/show/lib/ranges/eval.ts @@ -0,0 +1,87 @@ +import { Round, UnsignedInteger, ValidHit } from "dc-ranges-types"; +import { DividerHitEval, HitEval, RingsTargetHiddenHitEval } from "./types"; +import { floor, round as mRound } from "../math"; + +function getRingsEval(hit: ValidHit): RingsTargetHiddenHitEval { + return { + kind: "rings", + id: hit.id, + rings: `${floor(hit.rings, 1).toFixed(1)}${hit.innerTen ? '*' : ''}` + }; +} + +function getDividerEval(decimals: UnsignedInteger, hit: ValidHit): DividerHitEval { + return { + kind: "divider", + id: hit.id, + divider: floor(hit.divisor, decimals) + }; +} + + +export function getHitEval(round: Round, hit?: ValidHit): HitEval | null { + if (!hit) return null; + + switch (round.mode.mode) { + case "rings": + case "target": + return getRingsEval(hit); + + case "ringsDiv": + return { + ...getRingsEval(hit), + ...getDividerEval(0, hit), + kind: "ringsDiv", + + }; + + case "divider": + return getDividerEval(round.mode.decimals, hit); + + case "fullHidden": + case "hidden": + if (round.counts == false) { + return getRingsEval(hit); + } else { + return { + kind: "hidden", + id: hit.id, + } + } + + case "hundred": + return { + kind: "hundred", + id: hit.id, + hundred: Math.floor((floor(hit.rings, 1) - 1) * 10 + 1) + } + + case "decimal": + return { + ...getRingsEval(hit), + kind: "decimal", + decimal: Math.floor(hit.rings * 10) % 10 + } + + case "integerDecimal": + const integer = Math.floor(hit.rings); + const decimal = Math.round((hit.rings - integer) * 10); + return { + ...getRingsEval(hit), + kind: "integerDecimal", + integerTimesDecimal: integer * decimal + } + + case "circle": + const precision = 2; + return { + kind: "circle", + id: hit.id, + x: mRound(hit.x, precision), + y: mRound(hit.y, precision) + } + + default: + return null; + } +} \ No newline at end of file diff --git a/frontend/src/app/show/lib/ranges/index.ts b/frontend/src/app/show/lib/ranges/index.ts new file mode 100644 index 00000000..710fdd60 --- /dev/null +++ b/frontend/src/app/show/lib/ranges/index.ts @@ -0,0 +1,27 @@ +import { Hits, Index, Integer, Range, Round } from "dc-ranges-types"; + +export function getRoundName(data: Range, roundId?: Index): string | null { + if (!data.active) return null; + if (!data.discipline) return "Unbekannt"; + if (!roundId) roundId = data.round; + const round = data.discipline.rounds[roundId]; + if (!round) return "Unbekannt"; + return round.name; +} + +export function getSeriesId(hitId: Index, hitCount: Integer): Index { + return Math.floor((hitId - 1) / hitCount); +} + +export function getHitsOfSeries(round: Round, hits: Hits, seriesId: Integer): Hits { + const hitCount = round.hitsPerSum; + return hits.filter( + (hit) => getSeriesId(hit.id, hitCount) === seriesId + ); +} + +export function getLatestSeriesId(round: Round, hits: Hits): Index { + if (hits.length === 0) return 0; + const maxId = Math.max(...hits.map(h => h.id)); + return getSeriesId(maxId, round.hitsPerSum); +} diff --git a/frontend/src/app/show/lib/ranges/types.ts b/frontend/src/app/show/lib/ranges/types.ts new file mode 100644 index 00000000..f9c43c91 --- /dev/null +++ b/frontend/src/app/show/lib/ranges/types.ts @@ -0,0 +1,51 @@ +import { Index, UnsignedInteger, UnsignedNumber } from "dc-ranges-types" + +export type BaseHitEval = { + id: Index + kind: string +} + +export type RingsTargetHiddenHitEval = BaseHitEval & { + kind: "rings" + rings: string +} + +export type DividerHitEval = BaseHitEval & { + divider: UnsignedNumber + kind: "divider" +} + +export type HundredHitEval = BaseHitEval & { + hundred: UnsignedInteger + kind: "hundred" +} + +export type CircleHitEval = BaseHitEval & { + x: number, + y: number + kind: "circle" +} + +export type HiddenHitEval = BaseHitEval & { + kind: "hidden" +} + +export type DecimalHitEval = BaseHitEval & { + decimal: UnsignedInteger + rings: string, + kind: "decimal" +} + +export type IntegerTimesDecimalHitEval = BaseHitEval & { + integerTimesDecimal: UnsignedInteger + rings: string, + kind: "integerDecimal" +} + +export type RingsDivHitEval = BaseHitEval & { + kind: "ringsDiv", + rings: string, + divider: UnsignedNumber, +}; + +export type HitEval = RingsTargetHiddenHitEval | DividerHitEval | HundredHitEval | CircleHitEval | DecimalHitEval | IntegerTimesDecimalHitEval | RingsDivHitEval | HiddenHitEval; diff --git a/logger/package-lock.json b/logger/package-lock.json index db8aeb44..b26c7db3 100644 --- a/logger/package-lock.json +++ b/logger/package-lock.json @@ -9,12 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -25,13 +25,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/atomic-sleep": { @@ -59,9 +59,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -134,9 +134,9 @@ } }, "node_modules/pino": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.2.1.tgz", - "integrity": "sha512-Tjyv76gdUe2460dEhtcnA4fU/+HhGq2Kr7OWlo2R/Xxbmn/ZNKWavNWTD2k97IE+s755iVU7WcaOEIl+H3cq8w==", + "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", @@ -317,9 +317,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/logger/package.json b/logger/package.json index 9be599fc..cb051194 100644 --- a/logger/package.json +++ b/logger/package.json @@ -12,12 +12,12 @@ "license": "ISC", "description": "", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..52f7b818 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "displaycontroller", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "date-fns": "^4.1.0" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..c4a64748 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "date-fns": "^4.1.0" + } +} diff --git a/ranges/cache/package-lock.json b/ranges/cache/package-lock.json index 58345c99..0ca5befe 100644 --- a/ranges/cache/package-lock.json +++ b/ranges/cache/package-lock.json @@ -14,10 +14,10 @@ "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", "dc-table-watcher": "file:../../database/tableWatcher", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -27,15 +27,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -43,14 +43,14 @@ "name": "dc-db-smdb", "version": "1.0.0", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -63,8 +63,8 @@ "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -73,12 +73,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -87,7 +87,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -133,13 +133,13 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/anymatch": { @@ -261,9 +261,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -663,9 +663,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" } diff --git a/ranges/cache/package.json b/ranges/cache/package.json index 8b2c6bc9..5bd73b57 100644 --- a/ranges/cache/package.json +++ b/ranges/cache/package.json @@ -15,10 +15,10 @@ "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", "dc-table-watcher": "file:../../database/tableWatcher", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } diff --git a/ranges/log/Dockerfile b/ranges/log/Dockerfile index e3d9a194..44f54cdd 100644 --- a/ranges/log/Dockerfile +++ b/ranges/log/Dockerfile @@ -35,10 +35,17 @@ COPY --from=range-types . . RUN npm ci --loglevel=info && \ npm run build; +FROM deps-logger AS deps-streams +WORKDIR /app/streams +COPY --from=streams . . +RUN npm ci --loglevel=info && \ + npm run build; + FROM base AS local-deps WORKDIR /app COPY --from=deps-logger /app/logger /app/logger +COPY --from=deps-streams /app/streams /app/streams COPY --from=deps-local-db /app/database/local /app/database/local COPY --from=deps-ranges-ttl /app/ranges/ttl /app/ranges/ttl COPY --from=deps-ranges-types /app/ranges/types /app/ranges/types diff --git a/ranges/log/docker-compose.yaml b/ranges/log/docker-compose.yaml index 93d1d856..7fd20251 100644 --- a/ranges/log/docker-compose.yaml +++ b/ranges/log/docker-compose.yaml @@ -20,6 +20,7 @@ services: - range-types=../types - range-ttl=../ttl - logger=../../logger + - streams=../../streams networks: - displaycontroller depends_on: diff --git a/ranges/log/package-lock.json b/ranges/log/package-lock.json index 26591baf..ec118af2 100644 --- a/ranges/log/package-lock.json +++ b/ranges/log/package-lock.json @@ -10,18 +10,23 @@ "license": "ISC", "dependencies": { "amqplib": "^0.10.9", + "csv-parse": "^6.1.0", + "date-fns": "^4.1.0", "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", "dc-ranges-ttl": "file:../ttl", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3", - "socket.io-client": "^4.8.3" + "dc-streams": "file:../../streams", + "dotenv": "^17.3.1", + "socket.io-client": "^4.8.3", + "typia": "^12.0.0" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", + "ts-patch": "^3.3.0", "tsc-alias": "^1.8.16", - "typescript": "^5.9.3" + "typescript": "~5.9.3" } }, "../../database/local": { @@ -29,15 +34,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -46,20 +51,34 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, + "../../streams": { + "name": "dc-streams", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.9", + "dc-logger": "file:../logger" + }, + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/node": "^25.5.0", + "typescript": "~5.9.3" + } + }, "../ttl": { "name": "dc-ranges-ttl", "version": "1.0.0", "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -68,13 +87,34 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", "typescript": "~5.9.3" } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -119,6 +159,12 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@types/amqplib": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz", @@ -130,13 +176,50 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", - "dev": true, + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typia/core": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@typia/core/-/core-12.0.0.tgz", + "integrity": "sha512-KtxUiqSqJVT6Eiyn56FL/nnAaRewHiYUx83PRz41CtI6w/P/DgvZhVe2kUjxjWjiaOirEGzu33KbztNf9APvCA==", + "license": "MIT", + "dependencies": { + "@typia/interface": "^12.0.0", + "@typia/utils": "^12.0.0" + } + }, + "node_modules/@typia/interface": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@typia/interface/-/interface-12.0.0.tgz", + "integrity": "sha512-Kxtlc2vM6mjoHza3DBjqNY02jMCB07BDCTc54qdHfr9sT/TYELsYEOzNblF7d7xeJRQrXw7e0R/3pJqik/zKWg==", + "license": "MIT" + }, + "node_modules/@typia/transform": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@typia/transform/-/transform-12.0.0.tgz", + "integrity": "sha512-BYl/FM1oZ5ZwyA4BrzqyGA6dOOFNae4pMWIgehOH+Ai55sdDB5kGTCRaN289FODrLbfs0UgUDunhIdbVVZN3nQ==", + "license": "MIT", + "dependencies": { + "@typia/core": "^12.0.0", + "@typia/interface": "^12.0.0", + "@typia/utils": "^12.0.0" + } + }, + "node_modules/@typia/utils": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@typia/utils/-/utils-12.0.0.tgz", + "integrity": "sha512-wb1av26sn6QzXXwVUz4mbWmy/cDc5AOySFol8XwO7VRIEZUcnuDW/3+LOFPdLiNyEIkOSvoJhi08NTecF6TcEg==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@typia/interface": "^12.0.0" } }, "node_modules/amqplib": { @@ -152,6 +235,45 @@ "node": ">=10" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -166,6 +288,12 @@ "node": ">= 8" } }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -176,6 +304,26 @@ "node": ">=8" } }, + "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/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -189,6 +337,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -202,12 +361,58 @@ "node": ">=8" } }, + "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-more-ints": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", "license": "MIT" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -233,6 +438,66 @@ "fsevents": "~2.3.2" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -243,6 +508,42 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/comment-json": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.5.1.tgz", + "integrity": "sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/csv-parse": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-6.1.0.tgz", + "integrity": "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dc-db-local": { "resolved": "../../database/local", "link": true @@ -259,6 +560,10 @@ "resolved": "../types", "link": true }, + "node_modules/dc-streams": { + "resolved": "../../streams", + "link": true + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -276,6 +581,18 @@ } } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -290,9 +607,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -301,6 +618,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/engine.io-client": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", @@ -323,6 +655,28 @@ "node": ">=10.0.0" } }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -350,6 +704,21 @@ "reusify": "^1.0.4" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -378,6 +747,16 @@ "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-tsconfig": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", @@ -404,6 +783,21 @@ "node": ">= 6" } }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -425,61 +819,249 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=8" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "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": { - "is-extglob": "^2.1.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/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": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", + "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge2": { "version": "1.4.1", @@ -505,12 +1087,37 @@ "node": ">=8.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, "node_modules/mylas": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", @@ -535,6 +1142,60 @@ "node": ">=0.10.0" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -571,6 +1232,22 @@ "node": ">=12" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -608,6 +1285,33 @@ ], "license": "MIT" }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -627,6 +1331,27 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "license": "MIT" }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -637,6 +1362,28 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -648,6 +1395,15 @@ "node": ">=0.10.0" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -672,6 +1428,60 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/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/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -727,6 +1537,72 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -740,6 +1616,25 @@ "node": ">=8.0" } }, + "node_modules/ts-patch": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-3.3.0.tgz", + "integrity": "sha512-zAOzDnd5qsfEnjd9IGy1IRuvA7ygyyxxdxesbhMdutt8AHFjD8Vw8hU2rMF89HX1BKRWFYqKHrO8Q6lw0NeUZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "global-prefix": "^4.0.0", + "minimist": "^1.2.8", + "resolve": "^1.22.2", + "semver": "^7.6.3", + "strip-ansi": "^6.0.1" + }, + "bin": { + "ts-patch": "bin/ts-patch.js", + "tspc": "bin/tspc.js" + } + }, "node_modules/tsc-alias": { "version": "1.8.16", "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", @@ -762,12 +1657,30 @@ "node": ">=16.20.2" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -776,11 +1689,44 @@ "node": ">=14.17" } }, + "node_modules/typia": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/typia/-/typia-12.0.0.tgz", + "integrity": "sha512-gmTfpAdq25LpG9E+JmYf6Ho+ePzqoi+0vLxgQyN8Et0vwZwAt7NQNyHA9cxVBhq3HN9j/7hHfuti67qup864LA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@typia/core": "^12.0.0", + "@typia/interface": "^12.0.0", + "@typia/transform": "^12.0.0", + "@typia/utils": "^12.0.0", + "commander": "^10.0.0", + "comment-json": "^4.2.3", + "inquirer": "^8.2.5", + "package-manager-detector": "^0.2.0", + "randexp": "^0.5.3" + }, + "bin": { + "typia": "lib/executable/typia.js" + }, + "peerDependencies": { + "typescript": ">=4.8.0 <5.10.0" + } + }, + "node_modules/typia/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "devOptional": true, "license": "MIT" }, "node_modules/url-parse": { @@ -793,6 +1739,51 @@ "requires-port": "^1.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/ranges/log/package.json b/ranges/log/package.json index 626d51b1..f4643ec3 100644 --- a/ranges/log/package.json +++ b/ranges/log/package.json @@ -4,24 +4,29 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc && tsc-alias" + "build": "ts-patch install && tsc && tsc-alias" }, "author": "Sven Finn", "license": "ISC", "description": "", "dependencies": { "amqplib": "^0.10.9", + "csv-parse": "^6.1.0", + "date-fns": "^4.1.0", "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", "dc-ranges-ttl": "file:../ttl", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3", - "socket.io-client": "^4.8.3" + "dc-streams": "file:../../streams", + "dotenv": "^17.3.1", + "socket.io-client": "^4.8.3", + "typia": "^12.0.0" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", + "ts-patch": "^3.3.0", "tsc-alias": "^1.8.16", - "typescript": "^5.9.3" + "typescript": "~5.9.3" } } diff --git a/ranges/log/src/cache/shooter.ts b/ranges/log/src/cache/shooter.ts index a99f43b7..56c33a35 100644 --- a/ranges/log/src/cache/shooter.ts +++ b/ranges/log/src/cache/shooter.ts @@ -1,5 +1,5 @@ import { LocalClient } from "dc-db-local"; -import { isShooter, mergeMaps, isShooterId, isInternalShooterByName, InternalShooter, ShooterId, InternalShooterByName } from "dc-ranges-types"; +import { isShooter, mergeMaps, InternalShooter, ShooterId, InternalShooterByName } from "dc-ranges-types"; export const shooterMap = new Map(); @@ -17,35 +17,24 @@ export async function updateShooters(client: LocalClient) { if (shooter.value.id === null) { continue; } - shooterTempMap.set(shooter.value.id, { firstName: shooter.value.firstName, lastName: shooter.value.lastName }); + shooterTempMap.set(shooter.value.id, { type: "byName", firstName: shooter.value.firstName, lastName: shooter.value.lastName }); } mergeMaps(shooterMap, shooterTempMap); } export function isSameShooter(shooterOne: InternalShooter | null, shooterTwo: InternalShooter | null): boolean { - if (shooterOne === null && shooterTwo === null) { - return true; - } else if (shooterOne === null || shooterTwo === null) { - return false; + if (shooterOne === null || shooterTwo === null) return shooterOne === null && shooterTwo === null; + if (shooterOne.type === "free" || shooterTwo.type === "free") return shooterOne.type === shooterTwo.type; + + if (shooterOne.type === "byId" && shooterTwo.type === "byId") { + return shooterOne.id === shooterTwo.id; } - const isShooterOneById = isShooterId(shooterOne); - const isShooterTwoById = isShooterId(shooterTwo); - if (isShooterOneById && isShooterTwoById) { - return shooterOne === shooterTwo; - } else if (isInternalShooterByName(shooterOne) && isInternalShooterByName(shooterTwo)) { - return shooterOne.firstName === shooterTwo.firstName && shooterOne.lastName === shooterTwo.lastName; - } else if (isShooterOneById && isInternalShooterByName(shooterTwo)) { - const shooter = shooterMap.get(shooterOne); - if (!shooter) { - return false; - } - return shooter.firstName === shooterTwo.firstName && shooter.lastName === shooterTwo.lastName; - } else if (isShooterTwoById && isInternalShooterByName(shooterOne)) { - const shooter = shooterMap.get(shooterTwo); - if (!shooter) { - return false; - } - return shooter.firstName === shooterOne.firstName && shooter.lastName === shooterOne.lastName; + + const shooterOneByName = shooterOne.type === "byId" ? shooterMap.get(shooterOne.id) : shooterOne; + const shooterTwoByName = shooterTwo.type === "byId" ? shooterMap.get(shooterTwo.id) : shooterTwo; + if (!shooterOneByName || !shooterTwoByName) { + return false; } - return false; + + return shooterOneByName.firstName === shooterTwoByName.firstName && shooterOneByName.lastName === shooterTwoByName.lastName; } \ No newline at end of file diff --git a/ranges/log/src/index.ts b/ranges/log/src/index.ts index 71c71e84..5fadf4cd 100644 --- a/ranges/log/src/index.ts +++ b/ranges/log/src/index.ts @@ -1,17 +1,16 @@ -import { logReaderStream } from "./streams/logReader"; import amqp from "amqplib"; import { logger } from "dc-logger"; - import "./cache/updater"; // Import the updater to start the caching import { ServerStateStream } from "./streams/serverState"; import { createLocalClient } from "dc-db-local"; -import { LogParserStream } from "./streams/logParser"; -import { MulticastStream } from "./streams/multicastRecv"; import { RangeMerger } from "./streams/mergeRange"; -import { AccumulateRanges } from "./streams/accumulateRanges"; -import { DebounceStream } from "./streams/debounce"; -import { RabbitSenderStream } from "./streams/rabbitSender"; import { MulticastAccumulate } from "./streams/multicastAcc"; +import { LogTailStream } from "./streams/logTail"; +import { CsvLineStream } from "./streams/csvLines"; +import { LogLineStream } from "./streams/logLine"; +import { RangeStateStream } from "./streams/rangeState"; +import { DebounceTransform, RabbitMqReceiver, RabbitMqWriter } from "dc-streams"; +import { InternalRange, isInternalRange } from "dc-ranges-types"; async function main() { @@ -19,21 +18,28 @@ async function main() { const connection = await amqp.connect("amqp://rabbitmq"); const localClient = createLocalClient(); - const merger = new RangeMerger(localClient); + const rangeMerger = new RangeMerger(localClient); const serverState = new ServerStateStream(); - const logReader = new logReaderStream(localClient); - const logParser = new LogParserStream(); - const accumulateRanges = new AccumulateRanges(); - const debounceRanges = new DebounceStream(300); - serverState.pipe(logReader).pipe(logParser).pipe(accumulateRanges).pipe(debounceRanges).pipe(merger); - - const multicastRecv = new MulticastStream(connection); + const logTail = new LogTailStream(localClient); + const csvLines = new CsvLineStream(); + const logLines = new LogLineStream(); + const rangeState = new RangeStateStream(); + const rangeDebounce = new DebounceTransform((value: InternalRange) => value.rangeId.toString(), 300); + serverState + .pipe(logTail) + .pipe(csvLines) + .pipe(logLines) + .pipe(rangeState) + .pipe(rangeDebounce) + .pipe(rangeMerger); + + const multicastRecv = new RabbitMqReceiver(connection, ["ranges.multicast"], isInternalRange); const multicastAccumulate = new MulticastAccumulate(); - multicastRecv.pipe(multicastAccumulate).pipe(merger); + multicastRecv.pipe(multicastAccumulate).pipe(rangeMerger); - const sender = new RabbitSenderStream(connection); - merger.pipe(sender); + const rangePublisher = new RabbitMqWriter(connection, ["ranges.log"], (value: InternalRange) => value.rangeId.toString()); + rangeMerger.pipe(rangePublisher); logger.info("Connected to rabbitmq"); } diff --git a/ranges/log/src/streams/accumulateRanges.ts b/ranges/log/src/streams/accumulateRanges.ts deleted file mode 100644 index 4eac3fb5..00000000 --- a/ranges/log/src/streams/accumulateRanges.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Transform } from "stream"; -import { LogInternalRange, LogMessage } from "../types"; -import { logger } from "dc-logger"; -import { INVALID_HIT_POS } from "dc-ranges-types"; - -export class AccumulateRanges extends Transform { - private ranges: Map = new Map(); - - constructor() { - super({ objectMode: true }); - } - - _transform(chunk: LogMessage, encoding: BufferEncoding, callback: () => void): void { - if (chunk.action === "reset") { - this.ranges.clear(); - callback(); - return; - } - if (chunk.timestamp < (new Date(Date.now() - 24 * 60 * 60 * 1000))) { - callback(); - return; - } - if (chunk.targetId !== this.ranges.get(chunk.rangeId)?.targetId) { - this.ranges.delete(chunk.rangeId); - } - const rangeData: LogInternalRange = this.ranges.get(chunk.rangeId) || { - rangeId: chunk.rangeId, - targetId: chunk.targetId, - discipline: null, - shooter: null, - hits: [], - startListId: null, - source: "log", - ttl: 15000, - last_update: chunk.timestamp, - } - rangeData.shooter = chunk.shooter.id || null; // Id is 0 if free - rangeData.discipline = { disciplineId: chunk.discipline.id, roundId: chunk.round.id }; - while (rangeData.hits.length - 1 < chunk.round.id) { - rangeData.hits.push([]); - } - if (chunk.action === "insert") { - logger.info(`Adding hit ${chunk.hit.id} to range ${chunk.rangeId}`); - if (rangeData.hits[chunk.round.id] === null) { - rangeData.hits[chunk.round.id] = []; - } - if (chunk.hit.x >= INVALID_HIT_POS[0] && chunk.hit.y >= INVALID_HIT_POS[1]) { - - rangeData.hits[chunk.round.id]?.push({ - id: chunk.hit.id, - valid: false, - }); - } else { - rangeData.hits[chunk.round.id]?.push({ - id: chunk.hit.id, - x: chunk.hit.x, - y: chunk.hit.y, - divisor: chunk.hit.divisor, - rings: chunk.hit.rings, - innerTen: chunk.hit.innerRing, - valid: true - }); - } - rangeData.hits[chunk.round.id]?.forEach((hit, index) => { - hit.id = index + 1; - return hit; - }) - - } else { - logger.info(`Removing hit ${chunk.hit.id} from range ${chunk.rangeId}`); - if (rangeData.hits[chunk.round.id] === null) { - callback(); - return; - } - rangeData.hits[chunk.round.id] = rangeData.hits[chunk.round.id]?.filter(hit => hit.id !== chunk.hit.id) || null; - rangeData.hits[chunk.round.id]?.forEach(hit => { - if (hit.id > chunk.hit.id) { - hit.id--; - } - return hit; - }); - } - rangeData.hits[chunk.round.id]?.sort((a, b) => a.id - b.id); - rangeData.last_update = chunk.timestamp; - this.ranges.set(chunk.rangeId, rangeData); - this.push(rangeData); - callback(); - } -} \ No newline at end of file diff --git a/ranges/log/src/streams/csvLines.ts b/ranges/log/src/streams/csvLines.ts new file mode 100644 index 00000000..e3d2eb68 --- /dev/null +++ b/ranges/log/src/streams/csvLines.ts @@ -0,0 +1,37 @@ +import { logger } from "dc-logger"; +import { RawLogMessage } from "../types"; +import { parse } from "csv-parse/sync"; +import { TypedTransform } from "dc-streams"; + + +export class CsvLineStream extends TypedTransform { + constructor() { + super(); + } + + _transform(line: string, encoding: BufferEncoding, callback: (error?: Error | null, data?: any) => void): void { + if (line === "LOG_RESET") { + const message: RawLogMessage = { action: "reset" }; + this.push(message); + return callback(); + + } + try { + const records = parse(line, { + delimiter: ";", + relax_column_count: true, + trim: true, + }); + + if (records.length > 0) { + this.push({ + action: "line", + parts: records[0], + } as RawLogMessage); + } + } catch (err) { + logger.error(`Error parsing CSV: ${err}`); + } + callback(); + } +} \ No newline at end of file diff --git a/ranges/log/src/streams/debounce.ts b/ranges/log/src/streams/debounce.ts deleted file mode 100644 index d0d5f232..00000000 --- a/ranges/log/src/streams/debounce.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { InternalRange } from "dc-ranges-types"; -import { Transform, TransformCallback } from "stream"; -import { logger } from "dc-logger"; - -type RangeDebounce = { - data: InternalRange; - debounce: NodeJS.Timeout; -} - -export class DebounceStream extends Transform { - private ranges: Map = new Map(); - private debounceTime: number; - - constructor(debounceTime: number) { - super({ objectMode: true }); - this.debounceTime = debounceTime; - } - - _transform(chunk: InternalRange, encoding: BufferEncoding, callback: TransformCallback): void { - logger.debug(`Received range ${chunk.rangeId} from RangeDataStream`); - if (this.ranges.has(chunk.rangeId)) { - if (JSON.stringify(this.ranges.get(chunk.rangeId)!.data) === JSON.stringify(chunk)) { - logger.debug(`Skipping range ${chunk.rangeId} due to no changes`); - callback(); - return; - } - clearTimeout(this.ranges.get(chunk.rangeId)!.debounce); - } - this.ranges.set(chunk.rangeId, { - data: chunk, - debounce: setTimeout(() => { - this.push(chunk); - this.ranges.delete(chunk.rangeId); - }, this.debounceTime) - }); - callback(); - } -} \ No newline at end of file diff --git a/ranges/log/src/streams/logLine.ts b/ranges/log/src/streams/logLine.ts new file mode 100644 index 00000000..32b149ba --- /dev/null +++ b/ranges/log/src/streams/logLine.ts @@ -0,0 +1,126 @@ +import { isValid, parse } from "date-fns"; +import { isLogLine, LogLine, LogMessage, RawLogMessage } from "../types"; +import { logger } from "dc-logger"; +import { Index, INVALID_HIT_POS, RangeId, ShooterId, UnsignedInteger } from "dc-ranges-types"; +import { TypedTransform } from "dc-streams"; + +const LOG_LINE_PARTS = { + RANGE_ID: 0, + DISCIPLINE_ID: 3, + ROUND_ID: 5, + SHOOTER_ID: 7, + HIT_ID: 13, + TIME: 14, + DATE: 15, + RINGS: 16, + HIT_X: 17, + HIT_Y: 18, + DIVISOR: 19, + TARGET_ID: 26, + ACTION: 27, + innerTen: 28, +} +const EXPECTED_COLUMN_COUNT = 30; + +export class LogLineStream extends TypedTransform { + constructor() { + super(); + } + + private transformAction(action: string): "insert" | "delete" { + if (action === "i") return "insert"; + if (action === "d") return "delete"; + throw new Error(`Unknown action "${action}" in log line.`); + } + + private intOrThrow(value: string): number { + const parsed = parseInt(value, 10); + if (isNaN(parsed)) { + throw new Error(`Value "${value}" is not a valid integer.`); + } + return parsed; + } + + private parseDeFloat(value: string): number { + const normalized = value.replace(/,/g, "."); + const parsed = parseFloat(normalized); + if (isNaN(parsed)) { + throw new Error(`Value "${value}" is not a valid float.`); + } + return parsed; + } + + private parseTimestamp(dateStr: string, timeStr: string): Date { + const combined = `${dateStr.trim()} ${timeStr.trim()}`; + const dt = parse( + combined, + "d.M.yyyy H:m:s.SSS", + new Date() + ); + + if (!isValid(dt)) { + throw new Error(`Invalid timestamp: ${combined}`); + } + + return dt; + } + + private createLogLine(parts: string[]): LogLine | null { + if (parts.length != EXPECTED_COLUMN_COUNT) { + logger.warn(`Unexpected number of columns in log line. Expected ${EXPECTED_COLUMN_COUNT}, got ${parts.length}. Line: ${parts.join(",")}`); + return null; + } + + try { + const timestamp = this.parseTimestamp(parts[LOG_LINE_PARTS.DATE], parts[LOG_LINE_PARTS.TIME]); + if (timestamp < (new Date(Date.now() - 24 * 60 * 60 * 1000))) { + logger.debug(`Skipping log line with old timestamp: ${timestamp.toISOString()}`); + return null; + } + + const hitX = this.parseDeFloat(parts[LOG_LINE_PARTS.HIT_X]); + const hitY = this.parseDeFloat(parts[LOG_LINE_PARTS.HIT_Y]); + + const line: LogLine = { + action: this.transformAction(parts[LOG_LINE_PARTS.ACTION]), + rangeId: this.intOrThrow(parts[LOG_LINE_PARTS.RANGE_ID]) as RangeId, + targetId: parts[LOG_LINE_PARTS.TARGET_ID], + shooterId: this.intOrThrow(parts[LOG_LINE_PARTS.SHOOTER_ID]) as ShooterId, + discipline: { + disciplineId: this.intOrThrow(parts[LOG_LINE_PARTS.DISCIPLINE_ID]) as Index, + roundId: this.intOrThrow(parts[LOG_LINE_PARTS.ROUND_ID]) as Index, + }, + hit: { + id: this.intOrThrow(parts[LOG_LINE_PARTS.HIT_ID]) as UnsignedInteger, + x: hitX / 100, + y: hitY / 100, + divisor: this.intOrThrow(parts[LOG_LINE_PARTS.DIVISOR]) as UnsignedInteger, + rings: this.parseDeFloat(parts[LOG_LINE_PARTS.RINGS]), + innerTen: parts[LOG_LINE_PARTS.innerTen] === "IZ", + valid: hitX < INVALID_HIT_POS[0] && hitY < INVALID_HIT_POS[1], + }, + timestamp, + } + if (!isLogLine(line)) { + logger.warn(`Parsed log line does not conform to LogLine type. Line: ${JSON.stringify(line)}`); + return null; + } + return line; + } catch (error) { + logger.warn(`Failed to parse log line: ${error instanceof Error ? error.message : String(error)}`); + return null; + } + } + _transform(msg: RawLogMessage, encoding: BufferEncoding, callback: (error?: Error | null, data?: any) => void): void { + if (msg.action === "reset") { + this.push({ action: "reset" }); + return callback(); + } + + const logLine = this.createLogLine(msg.parts); + if (logLine) { + this.push(logLine); + } + callback(); + } +} \ No newline at end of file diff --git a/ranges/log/src/streams/logParser.ts b/ranges/log/src/streams/logParser.ts deleted file mode 100644 index 1db42f93..00000000 --- a/ranges/log/src/streams/logParser.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Transform } from "stream"; -import { logger } from "dc-logger"; -import { LogMessage, LogLine } from "../types"; - - -export class LogParserStream extends Transform { - constructor() { - super({ objectMode: true }); - } - - _transform(line: string, encoding: BufferEncoding, callback: (error?: Error | null, data?: any) => void): void { - if (line === "LOG_RESET") { - const message: LogMessage = { action: "reset" }; - this.push(message); - callback(); - return - } - logger.debug(`Parsing line: ${line}`); - const values = line.split(";"); - for (let i = 0; i < values.length; i++) { - values[i] = values[i].replace(/^"|"$/g, ""); - values[i] = values[i].trim(); - } - if (values.length != 30) { - logger.warn(`log line does not have correct length: ${values.length} != 30`); - callback(); - return; - } - try { - const logInformation: LogLine = { - rangeId: parseInt(values[0]), - targetId: this.parseTargetId(values[26]) || -1, - action: values[27] == "i" ? "insert" : "delete", - shooter: { - name: values[6], - id: Number(values[7]), - team: values[8], - club: values[9], - class: { - name: values[10], - id: parseInt(values[11]), - }, - }, - discipline: { - name: values[2], - id: parseInt(values[3]), - }, - round: { - name: values[4], - id: parseInt(values[5]), - }, - hit: { - id: parseInt(values[13]), - x: parseFloat(values[17]) / 100, - y: parseFloat(values[18]) / 100, - divisor: parseFloat(values[19]), - innerRing: values[28] === "IZ", - rings: parseFloat(values[16].replace(/,/gi, ".")), - }, - timestamp: this.parseTimeStamp(values[15], values[14]) || new Date(), - - } - this.push(logInformation); - } catch (error) { - logger.warn(`Error occurred whilst trying to parse line: ${error}`); - } - callback(); - } - - private parseTargetId(targetId: string): undefined | number { - if (targetId.match("0x") != null) { - const intTargetId = parseInt(targetId, 16); - if (intTargetId > 2147483647) { - return intTargetId - 4294967296; - } - return intTargetId; - } - logger.warn("Failed to parse targetId"); - return undefined; - } - - private parseTimeStamp(dateStr: string, timeStr: string): undefined | Date { - if (dateStr.length == 0 || timeStr.length == 0) { - logger.warn("Failed to parse timestamp, len=0"); - return undefined; - } - const date = dateStr.split(".").map((value) => parseInt(value)); - const tempTime = timeStr.split(":"); - if (date.length != 3 || tempTime.length != 3) { - logger.warn(`Failed to parse timestamp ${date} ${tempTime}`); - return undefined; - } - const time = [tempTime[0], tempTime[1], ...tempTime[2].split(".")].map((value) => parseInt(value)); - if (date.includes(NaN) || time.includes(NaN)) { - logger.warn("Failed to parse timestamp, NAN"); - return undefined; - } - return new Date(date[2], date[1] - 1, date[0], time[0], time[1], time[2], time[3]); - } -} \ No newline at end of file diff --git a/ranges/log/src/streams/logReader.ts b/ranges/log/src/streams/logTail.ts similarity index 90% rename from ranges/log/src/streams/logReader.ts rename to ranges/log/src/streams/logTail.ts index 3fb3c07b..d2b8028a 100644 --- a/ranges/log/src/streams/logReader.ts +++ b/ranges/log/src/streams/logTail.ts @@ -1,16 +1,16 @@ import { ChildProcess, spawn } from "child_process"; import { LocalClient } from "dc-db-local"; import { logger } from "dc-logger"; +import { TypedDuplex } from "dc-streams"; import * as fs from "fs"; -import { Duplex } from "stream"; -export class logReaderStream extends Duplex { +export class LogTailStream extends TypedDuplex { private sshThread: ChildProcess | null = null; private localClient: LocalClient; private serverState: boolean = false; constructor(prisma: LocalClient) { - super({ objectMode: true }); + super(); this.localClient = prisma; } @@ -41,7 +41,7 @@ export class logReaderStream extends Duplex { } })).strValue; - this.sshThread = spawn("sshpass", ["-p", process.env.SM_SSH_PASS as string, "ssh", "-o", "StrictHostKeyChecking=no", `${process.env.SM_SSH_USER}@${serverIp}`, script]); + this.sshThread = spawn("sshpass", ["-p", process.env.SM_SSH_PASS as string, "ssh", "-T", "-o", "StrictHostKeyChecking=no", `${process.env.SM_SSH_USER}@${serverIp}`, script]); this.sshThread.on("exit", (code) => { if (this.serverState) { logger.warn(`SSH exit: ${code}`); diff --git a/ranges/log/src/streams/mergeRange.ts b/ranges/log/src/streams/mergeRange.ts index 3dd87eb5..01e21126 100644 --- a/ranges/log/src/streams/mergeRange.ts +++ b/ranges/log/src/streams/mergeRange.ts @@ -1,4 +1,3 @@ -import { Transform } from "stream"; import { TTLHandler } from "dc-ranges-ttl"; import { InternalDiscipline, InternalRange, isInternalOverrideDiscipline, isNormInternalDiscipline } from "dc-ranges-types"; import { LogInternalRange, MulticastInternalRange } from "../types"; @@ -6,15 +5,15 @@ import { LocalClient } from "dc-db-local"; import { isSameShooter } from "../cache/shooter"; import { getDisciplineId } from "../cache/overrides"; import { logger } from "dc-logger"; +import { TypedTransform } from "dc-streams"; -export class RangeMerger extends Transform { +export class RangeMerger extends TypedTransform { private multicastStates: Map> = new Map(); private logStates: Map = new Map(); - private targetIdBlacklist: Set = new Set(); private freeTimeout: number = 30 * 60 * 1000; constructor(localClient: LocalClient) { - super({ objectMode: true }); + super(); this._getFreeTimeout(localClient); } @@ -90,7 +89,7 @@ export class RangeMerger extends Transform { } // If range is free and the last shot was more than freeTimeout ago, consider it free // Do not blacklist target, as the shooter might not have left the range yet - if (logState.shooter === null && logState.last_update.getTime() < Date.now() - this.freeTimeout) return null; + if ((logState.shooter === null || logState.shooter.type === "free") && logState.last_update.getTime() < Date.now() - this.freeTimeout) return null; // We now have a valid state to merge return { diff --git a/ranges/log/src/streams/multicastAcc.ts b/ranges/log/src/streams/multicastAcc.ts index 0da2d301..783b4593 100644 --- a/ranges/log/src/streams/multicastAcc.ts +++ b/ranges/log/src/streams/multicastAcc.ts @@ -1,18 +1,18 @@ import { InternalRange, InternalShooter } from "dc-ranges-types"; -import { Transform } from "stream"; import { isSameShooter } from "../cache/shooter"; import { MulticastInternalRange } from "../types"; +import { TypedTransform } from "dc-streams"; type ShooterSince = { shooter: InternalShooter | null, since: Date, } -export class MulticastAccumulate extends Transform { +export class MulticastAccumulate extends TypedTransform { private shooters: Map = new Map(); constructor() { - super({ objectMode: true }); + super(); } _transform(chunk: InternalRange, encoding: BufferEncoding, callback: (error?: Error | null, data?: any) => void): void { diff --git a/ranges/log/src/streams/multicastRecv.ts b/ranges/log/src/streams/multicastRecv.ts deleted file mode 100644 index 83e2444d..00000000 --- a/ranges/log/src/streams/multicastRecv.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { isInternalRange } from "dc-ranges-types"; -import { ChannelModel } from "amqplib"; -import { logger } from "dc-logger"; -import { Readable } from "stream"; - -export class MulticastStream extends Readable { - - constructor(connection: ChannelModel) { - super({ objectMode: true }); - this.connect(connection); - } - - private async connect(connection: ChannelModel) { - const channel = await connection.createChannel(); - await channel.assertExchange("ranges.multicast", "fanout", { - durable: false, - }); - const queue = await channel.assertQueue("", { - exclusive: true, - }); - channel.bindQueue(queue.queue, "ranges.multicast", ""); - channel.consume(queue.queue, async (msg) => { - if (msg === null) { - return; - } - const message = JSON.parse(msg.content.toString()); - if (isInternalRange(message)) { - this.push(message); - } else { - logger.error("Received invalid multicast message"); - } - channel.ack(msg); - }); - } - - _read() { - // Do nothing - } -} \ No newline at end of file diff --git a/ranges/log/src/streams/rabbitSender.ts b/ranges/log/src/streams/rabbitSender.ts deleted file mode 100644 index b2e58d3e..00000000 --- a/ranges/log/src/streams/rabbitSender.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Writable } from "stream"; -import amqp, { ChannelModel } from "amqplib"; -import { InternalRange } from "dc-ranges-types"; -import { logger } from "dc-logger"; - -export class RabbitSenderStream extends Writable { - private channel: amqp.Channel | null = null; - private currentState: Map = new Map(); - private resendTimeout: Map = new Map(); - - constructor(connection: ChannelModel) { - super({ objectMode: true }); - this.connect(connection); - } - - private async connect(connection: ChannelModel) { - this.channel = await connection.createChannel(); - await this.channel.assertExchange("ranges.log", "fanout", { - durable: false, - }); - } - - _write(chunk: InternalRange, encoding: BufferEncoding, callback: (error?: Error | null) => void): void { - if (this.channel === null) { - logger.error("Channel not connected"); - callback(); - return; - } - if (this.currentState.has(chunk.rangeId) && this.currentState.get(chunk.rangeId) === JSON.stringify(chunk)) { - logger.debug(`Range ${chunk.rangeId} already sent`); - callback(); - return; - } - this.currentState.set(chunk.rangeId, JSON.stringify(chunk)); - if (this.resendTimeout.has(chunk.rangeId)) { - clearTimeout(this.resendTimeout.get(chunk.rangeId)!); - } - logger.info(`Sending range ${chunk.rangeId}`); - this.channel.publish("ranges.log", "", Buffer.from(JSON.stringify(chunk))); - this.resendTimeout.set(chunk.rangeId, setTimeout(() => { - this.currentState.delete(chunk.rangeId); - }, 5000)); - callback(); - } -} \ No newline at end of file diff --git a/ranges/log/src/streams/range-logs.sh b/ranges/log/src/streams/range-logs.sh index f0164e7b..81a01282 100644 --- a/ranges/log/src/streams/range-logs.sh +++ b/ranges/log/src/streams/range-logs.sh @@ -1,6 +1,7 @@ #!/bin/bash set -euo pipefail +set +m # This script is used to monitor the log files of the Meyton Shootmaster software. # It will print the new lines of the log files to the console. @@ -17,7 +18,8 @@ cleanup() { # If a tail process is running, kill it if [ -n "$tail_pid" ]; then kill "$tail_pid" 2>/dev/null - wait "$tail_pid" 2>/dev/null + wait "$tail_pid" 2>/dev/null || true + tail_pid= fi exit 0 } @@ -34,7 +36,8 @@ while true; do # If a tail process is running, kill it if [ -n "$tail_pid" ]; then kill "$tail_pid" 2>/dev/null - wait "$tail_pid" 2>/dev/null + wait "$tail_pid" 2>/dev/null || true + tail_pid= fi # Update the tail count and start a new tail process diff --git a/ranges/log/src/streams/rangeState.ts b/ranges/log/src/streams/rangeState.ts new file mode 100644 index 00000000..37e20df8 --- /dev/null +++ b/ranges/log/src/streams/rangeState.ts @@ -0,0 +1,115 @@ +import { LogInternalRange, LogLine, LogMessage } from "../types"; +import { logger } from "dc-logger"; +import { Hits, RangeId } from "dc-ranges-types"; +import { TypedTransform } from "dc-streams"; + +export class RangeStateStream extends TypedTransform { + private ranges: Map = new Map(); + + private createEmptyRange(chunk: LogLine): LogInternalRange { + return { + rangeId: chunk.rangeId, + targetId: chunk.targetId, + discipline: chunk.discipline, + shooter: chunk.shooterId ? { type: "byId", id: chunk.shooterId } : null, + hits: [], + startListId: null, + source: "log", + ttl: 15000, + last_update: chunk.timestamp, + } + } + + private addHitToRange(rangeData: LogInternalRange, chunk: LogLine): void { + if (chunk.action !== "insert") return; + + const roundId = chunk.discipline.roundId; + while (rangeData.hits.length <= roundId) { + rangeData.hits.push([]); + } + + const hits = rangeData.hits[roundId]; + const insertId = chunk.hit.id; + + // 1. Find insertion index BEFORE shifting + let insertIndex = hits.findIndex(hit => hit.id >= insertId); + if (insertIndex < 0) { + insertIndex = hits.length; + } + + for (const hit of hits) { + if (hit.id >= insertId) { + hit.id += 1; + } + } + + hits.splice(insertIndex, 0, { ...chunk.hit }); + hits.sort((a, b) => a.id - b.id); + } + + private removeHitFromRange(rangeData: LogInternalRange, chunk: LogLine): void { + if (chunk.action !== "delete") return; + + const roundId = chunk.discipline.roundId; + const hits = rangeData.hits[roundId]; + if (!hits) { + logger.warn(`Trying to remove hit from non - existing round ${roundId} in range ${chunk.rangeId} `); + return; + } + + const deleteId = chunk.hit.id; + + // 1. Try to remove hit with id = deleteId + const hitIndex = hits.findIndex(hit => hit.id === deleteId); + if (hitIndex !== -1) { + hits.splice(hitIndex, 1); + } else { + logger.warn( + `Deleting hole at id ${deleteId} in range ${chunk.rangeId}, round ${roundId} ` + ); + } + + // 2. Always shift hits with id > deleteId + for (const hit of hits) { + if (hit.id > deleteId) { + hit.id -= 1; + } + } + hits.sort((a, b) => a.id - b.id); + + } + + + _transform(chunk: LogMessage, encoding: BufferEncoding, callback: () => void): void { + if (chunk.action === "reset") { + logger.info("Received reset action, clearing all ranges"); + this.ranges.clear(); + return callback(); + } + let rangeData = this.ranges.get(chunk.rangeId); + + if (rangeData && rangeData.targetId !== chunk.targetId) { + this.ranges.delete(chunk.rangeId); + rangeData = undefined; + } + + if (!rangeData) { + logger.info(`Creating new range with id ${chunk.rangeId} for target ${chunk.targetId}`); + rangeData = this.createEmptyRange(chunk); + this.ranges.set(chunk.rangeId, rangeData); + } + + rangeData.shooter = chunk.shooterId ? { type: "byId", id: chunk.shooterId } : { type: "free" }; + rangeData.discipline = chunk.discipline; + rangeData.last_update = chunk.timestamp; + if (chunk.action === "insert") { + logger.info(`Adding hit ${chunk.hit.id} to range ${chunk.rangeId} `); + this.addHitToRange(rangeData, chunk); + } else { + logger.info(`Removing hit ${chunk.hit.id} from range ${chunk.rangeId} `); + this.removeHitFromRange(rangeData, chunk); + } + this.push(structuredClone(rangeData)); + callback(); + } +} \ No newline at end of file diff --git a/ranges/log/src/streams/serverState.ts b/ranges/log/src/streams/serverState.ts index a431eab5..fd5ae25a 100644 --- a/ranges/log/src/streams/serverState.ts +++ b/ranges/log/src/streams/serverState.ts @@ -1,12 +1,12 @@ import { logger } from "dc-logger"; +import { TypedReadable } from "dc-streams"; import { io, Socket } from "socket.io-client"; -import { Readable } from "stream"; -export class ServerStateStream extends Readable { +export class ServerStateStream extends TypedReadable { private socket: Socket; constructor() { - super({ objectMode: true }); + super(); this.socket = io("http://server-state/api/serverState", { path: "/api/serverState/ws" }); this.socket.on("connect", () => { logger.info("Connected to server state via socket.io"); diff --git a/ranges/log/src/types.ts b/ranges/log/src/types.ts index 2d87a932..746eb8aa 100644 --- a/ranges/log/src/types.ts +++ b/ranges/log/src/types.ts @@ -1,45 +1,35 @@ -import { InternalRange } from "dc-ranges-types" +import { isValid, parse } from "date-fns" +import { logger } from "dc-logger" +import { Hit, Index, Integer, InternalDiscipline, InternalRange, InternalShooter, INVALID_HIT_POS, RangeId, ShooterId, UnsignedInteger } from "dc-ranges-types" +import { createIs } from "typia" -export type LogMessage = LogLine | { +export type ResetAction = { action: "reset", } +export type RawLogLine = { + action: "line", + parts: string[], +} + +export type RawLogMessage = ResetAction | RawLogLine + +export type LogMessage = LogLine | ResetAction + export type LogLine = { action: "insert" | "delete", - rangeId: number, - targetId: number, - shooter: { - name: string, - id: number, - team: string, - club: string, - class: { - name: string, - id: number, - }, - }, - discipline: { - name: string, - id: number, - }, - round: { - name: string, - id: number, - }, - hit: { - id: number, - x: number, - y: number, - divisor: number, - innerRing: boolean, - rings: number, - }, + rangeId: RangeId, + targetId: string, + shooterId: ShooterId + discipline: InternalDiscipline + hit: Hit, timestamp: Date, - } +export const isLogLine = createIs() + export type LogInternalRange = InternalRange & { - targetId: number + targetId: string last_update: Date } diff --git a/ranges/log/tsconfig.json b/ranges/log/tsconfig.json index a1f0fc6d..100b77ea 100644 --- a/ranges/log/tsconfig.json +++ b/ranges/log/tsconfig.json @@ -1,12 +1,19 @@ { - "compilerOptions": { - "target": "ES2023", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "outDir": "./dist" - }, - "include": [ - "src/**/*.ts" + "compilerOptions": { + "target": "ES2023", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "outDir": "./dist", + "plugins": [ + { + "transform": "typia/lib/transform" + } ], + "skipLibCheck": true, + "strictNullChecks": true + }, + "include": [ + "src/**/*.ts" + ] } \ No newline at end of file diff --git a/ranges/merge/package-lock.json b/ranges/merge/package-lock.json index 0c86108c..51ee70ce 100644 --- a/ranges/merge/package-lock.json +++ b/ranges/merge/package-lock.json @@ -14,14 +14,14 @@ "dc-logger": "file:../../logger", "dc-ranges-ttl": "file:../ttl", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "socket.io": "^4.8.3" }, "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -31,15 +31,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -48,12 +48,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -61,7 +61,7 @@ "name": "dc-ranges-ttl", "version": "1.0.0", "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -70,7 +70,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -201,12 +201,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -541,9 +541,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1820,9 +1820,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unpipe": { diff --git a/ranges/merge/package.json b/ranges/merge/package.json index dcbcaf55..cf4fde7b 100644 --- a/ranges/merge/package.json +++ b/ranges/merge/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" }, @@ -22,7 +22,7 @@ "dc-logger": "file:../../logger", "dc-ranges-ttl": "file:../ttl", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "socket.io": "^4.8.3" } diff --git a/ranges/merge/src/cache/shooters.ts b/ranges/merge/src/cache/shooters.ts index 6c2a039d..be4aa6c5 100644 --- a/ranges/merge/src/cache/shooters.ts +++ b/ranges/merge/src/cache/shooters.ts @@ -1,6 +1,6 @@ import { LocalClient } from "dc-db-local"; -import { Shooter, isShooter, InternalShooter, isShooterId, mergeMaps } from "dc-ranges-types"; -export const shooters = new Map(); +import { Shooter, isShooter, InternalShooter, RangeShooter, mergeMaps } from "dc-ranges-types"; +export const shooters = new Map(); export async function updateShooters(localClient: LocalClient) { const newShooters = await localClient.cache.findMany({ @@ -8,7 +8,7 @@ export async function updateShooters(localClient: LocalClient) { type: "shooter", }, }); - const shooterTempMap = new Map(); + const shooterTempMap = new Map(); for (const shooter of newShooters) { if (!isShooter(shooter.value)) { continue; @@ -16,19 +16,28 @@ export async function updateShooters(localClient: LocalClient) { if (shooter.value.id === null) { continue; } - shooterTempMap.set(shooter.value.id, shooter.value); + shooterTempMap.set(shooter.value.id, { + type: "occupied", + id: shooter.value.id, + firstName: shooter.value.firstName, + lastName: shooter.value.lastName, + }); } mergeMaps(shooters, shooterTempMap); } -export function getShooter(shooter: InternalShooter | null): Shooter | null { +export function getShooter(shooter: InternalShooter | null): RangeShooter | null { if (shooter == null) { // Range is free return null; } - if (isShooterId(shooter)) { - return structuredClone(shooters.get(shooter) ?? null); + if (shooter.type === "free") { + return { type: "free" }; + } + if (shooter.type == "byId") { + return structuredClone(shooters.get(shooter.id) ?? null); } return { + type: "occupied", id: null, firstName: shooter.firstName, lastName: shooter.lastName, diff --git a/ranges/merge/src/rangeMan/index.ts b/ranges/merge/src/rangeMan/index.ts index 3859b7bc..c8284c0a 100644 --- a/ranges/merge/src/rangeMan/index.ts +++ b/ranges/merge/src/rangeMan/index.ts @@ -45,8 +45,8 @@ export class RangeManager { logger.error("Namespace not set in RangeManager"); return; } - this.namespace.to(`range:${data.id}`).volatile.emit('data', data); - this.namespace.to('all').volatile.emit('data', data); + this.namespace.to(`range:${data.id}`).emit('data', data); + this.namespace.to('all').emit('data', data); } public getRangeData(rangeId: number): Range { diff --git a/ranges/merge/src/rangeMerger/merge.ts b/ranges/merge/src/rangeMerger/merge.ts index 02e7c950..91807d05 100644 --- a/ranges/merge/src/rangeMerger/merge.ts +++ b/ranges/merge/src/rangeMerger/merge.ts @@ -1,5 +1,5 @@ import { TTLHandler } from "dc-ranges-ttl"; -import { ActiveRange, InactiveRange, Source } from "dc-ranges-types"; +import { ActiveRange, InactiveRange, RangeShooter, Source } from "dc-ranges-types"; import { InternalRange, Discipline, Shooter, StartList, Hits, Hit } from "dc-ranges-types"; import { getDiscipline } from "../cache/disciplines"; import { getShooter } from "../cache/shooters"; @@ -22,7 +22,7 @@ function mergeDiscipline(sourceData: SourceData): Discipline | null { return getDiscipline(getFirstNotNull(disciplines) || null); } -function mergeShooter(sourceData: SourceData): Shooter | null { +function mergeShooter(sourceData: SourceData): RangeShooter | null { const shooters = sourceData.map((source) => source.getMessage()?.shooter); return getShooter(getFirstNotNull(shooters) || null); } diff --git a/ranges/merge/src/server/index.ts b/ranges/merge/src/server/index.ts index 138ac7e3..85b31524 100644 --- a/ranges/merge/src/server/index.ts +++ b/ranges/merge/src/server/index.ts @@ -7,6 +7,7 @@ import { Server } from 'socket.io'; import { isStartList } from 'dc-ranges-types'; const app: Express = express(); +app.use(express.json()); const server = createServer(app); const io = new Server(server, { path: '/api/ranges/ws' @@ -19,10 +20,15 @@ io.on('connection', (socket) => { let ranges: number[] | null = null; if (handshakeRanges) { - const rawRanges = Array.isArray(handshakeRanges) ? handshakeRanges : JSON.parse(handshakeRanges); - ranges = rawRanges - .map((r: any) => parseInt(r.toString())) - .filter((n: number) => !isNaN(n)); + try { + const rawRanges = Array.isArray(handshakeRanges) ? handshakeRanges : JSON.parse(handshakeRanges); + ranges = rawRanges + .map((r: any) => parseInt(r.toString())) + .filter((n: number) => !isNaN(n)); + } catch (error) { + logger.warn(`Invalid ranges JSON in handshake: ${error}`); + ranges = null; + } } setTimeout(() => { rangeManager.addSocket(socket, ranges); @@ -67,7 +73,7 @@ app.get('/api/ranges/known/:rangeMac', async (req: Request, res: Response) => { app.post('/api/ranges/known/:rangeMac', async (req: Request, res: Response) => { const rangeMac: string = req.params.rangeMac; - const rangeId = parseInt(req.body); + const rangeId = parseInt(req.body?.rangeId); if (isNaN(rangeId)) { res.status(400).send('Invalid range ID'); return; diff --git a/ranges/multicast/Dockerfile b/ranges/multicast/Dockerfile index e1e7e0fe..a33cc49e 100644 --- a/ranges/multicast/Dockerfile +++ b/ranges/multicast/Dockerfile @@ -27,11 +27,18 @@ COPY --from=range-types . . RUN npm ci --loglevel=info && \ npm run build; +FROM deps-logger AS deps-streams +WORKDIR /app/streams +COPY --from=streams . . +RUN npm ci --loglevel=info && \ + npm run build; + FROM base AS local-deps WORKDIR /app COPY --from=deps-logger /app/logger /app/logger COPY --from=deps-local-db /app/database/local /app/database/local +COPY --from=deps-streams /app/streams /app/streams COPY --from=deps-ranges-types /app/ranges/types /app/ranges/types FROM local-deps AS deps diff --git a/ranges/multicast/README.md b/ranges/multicast/README.md new file mode 100644 index 00000000..de0b7e1a --- /dev/null +++ b/ranges/multicast/README.md @@ -0,0 +1,212 @@ +# dc-ranges-multicast: Multicast interface + +This interface is used by the display controller to read the ShootMaster systems’ multicast protocol and derive the current state of a shooting range from it. It provides structured information about disciplines, shooters, and start lists. + +The extraction of range state information is based on heuristic, text-based analysis of the multicast packet content rather than a structured protocol implementation. This approach prioritizes long-term compatibility and robustness across ShootMaster versions. + +This approach results in a number of limitations and edge cases, which are described in the [Known Limitations](#known-limitations) section below. In practice, these limitations do not affect typical real-world installations, but they should be considered when integrating or extending this interface. + +To support this, the **dc-ranges-multicast-proxy** is used. This component performs a basic pre-filtering of multicast packets and forwards the relevant traffic into the display controller’s internal network. + +--- + +## Design Goals and Rationale + +Since the multicast protocol used by the ShootMaster system is undocumented and may change between system versions, fully reverse engineering it would be complex, fragile, and difficult to maintain. +In addition, the display controller can obtain most additional information through other interfaces provided by the ShootMaster server. + +The multicast interface is designed to be lightweight and focused on extracting necessary information until the shooter takes the first shot and scoring data becomes available. After that point, the display controller can rely on more structured and authoritative interfaces to obtain detailed information about the range state. + +Therefore, the multicast interface is not intended to provide a complete or fully verified representation of the range state. Instead, it serves as an interface for detecting if a range is active, and if so, which discipline, start list, and shooter are currently present at the range. This information is sufficient for the display controller to display basic information about the range, which gets enriched with more detailed information from other interfaces once the shooter starts shooting. + +This allows for a more robust and maintainable implementation that is less likely to break due to protocol changes. +For this reason, the multicast interface is designed with the following goals: + +- Avoid full protocol reverse engineering. +- Extract only the required information. + Data that is available through other interfaces should not be processed here. +- Remain robust against protocol changes. + The interface should support a wide range of ShootMaster versions and tolerate partially corrupt or unexpected data. +- Avoid false positives. + The interface should not report incorrect information about the range state, even if this means that some information cannot be extracted in certain edge cases. + +## ShootMaster Multicast Protocol + +For its internal communication, the ShootMaster system uses a proprietary multicast protocol. The protocol appears to use a custom binary serialization format and is not human-readable. In addition, there is no official documentation available, which makes custom implementations complicated. + +The protocol is used, among other purposes, by the ShootMaster server to send control commands to each range. These commands include tasks such as assigning shooters, changing disciplines, etc. Most server-to-range communication is performed using this protocol. Multicast is used for broadcast messages affecting multiple ranges, while unicast is used when a command targets a specific range. + +As part of this protocol, each shooting range periodically broadcasts its current state via the multicast address **224.0.0.1** on port **49497**. This broadcast is used by other systems, such as the official ShootMaster display controller, to visualize the range state. Because the ranges broadcast their state independently, the visualization can operate without a central ShootMaster server, which may not be present in smaller installations. + +Within the ShootMaster ecosystem, a network can contain up to **416 ranges**. Each range is identified by a unique “range number” in the range from **1 to 416**. This identifier is used consistently across the entire ShootMaster system, including in the range broadcast. + +## Packet Processing + +### Message Model + +Without a formal protocol definition, the broadcast sent by each range can be treated as an opaque blob, which contains a mixture of binary data and human-readable strings. The exact structure of the packet is unknown and may vary between system versions. However, it is observed that the packet contains a number of textual fields encoded in Windows-1252. These fields include: + +- The discipline name +- Start list name +- Shooter names +- Additional, discipline-specific keywords such as *Vorbereitung/Probe* or *Wertung* +- Shooter-specific information such as club names + +The exact layout of these fields within the packet is not fixed and may change between system versions. Therefore, the interface should not rely on fixed offsets or a specific packet structure to extract this information. + +--- + +#### Observed naming properties + +Based on observations across supported ShootMaster versions and real-world installations, the following characteristics are typically true for textual values contained in multicast packets. These characteristics form the basis for some of the heuristics used during range state extraction. + +The following observations apply to discipline names, shooter names, and start list names: + +1. Discipline names are rather short ("LG 40", "KK 50m", etc.) +2. Start list names are often long. +3. Shooter names are stored in the packet as "Lastname, Firstname" +4. None of the values are guaranteed to be unique, as multiple disciplines, start lists, or shooters with the same name may exist. +5. The ShootMaster system does not enforce any specific formatting for these values, neither in terms of content nor in length. + +These characteristics are not formally guaranteed by the protocol but are consistently observed across supported ShootMaster versions. The heuristic extraction logic described below relies on these observations. + +Since the packet only contains textual values and does not include unique identifiers for these entities, it is not possible to reliably distinguish between entities that share the same name based on packet content alone. Therefore, special care has to be taken in the processing logic to handle such cases, as described in the range state extraction section below. + +### Processing Pipeline + +The **dc-ranges-multicast-proxy** receives the packets on the specified multicast address. It re-encodes the packet from Windows-1252 to a UTF-8 string and sends the following to the main interface container: + +- The packet content as a UTF-8 string representation +- The MAC address of the sender +- The current IP address of the range + +#### Range Identification + +The display controller maintains a mapping between a range’s MAC address and its range number. The range number is determined using this mapping as follows: + +1. The system queries the mapping for a range entry matching the sender’s MAC address. +2. If a matching entry is found, the associated RangeId is used. +3. If no entry exists for the MAC address, the system attempts a fallback strategy: + - The last octet of the sender’s IP address is extracted (for example, `20` from `192.168.10.20`). + - This value is interpreted as a candidate RangeId. + - If this RangeId is not already assigned in the database, it is accepted. +4. When a new RangeId is determined through the fallback strategy, a new mapping between the MAC address and the RangeId is created and stored in the database. + +This fallback strategy follows a ip address convention defined by the manufacturer of the system, where the last octet of the range’s IP address corresponds to its range number for ranges 1 - 159. (see [Standard IP-Adressen von Geräten in +einem Meyton Netzwerk](https://software.meyton.info/wp-content/uploads/Upload/Manuals/DE/Inbetriebnahme/Standard_IP-Adressen_von_Geraeten_in_einem_Meyton_Netzwerk.pdf)) As most installations are smaller than 160 ranges, this convention is widely used in practice. However, it is not guaranteed to be followed in all installations, and therefore the fallback strategy only accepts the candidate RangeId if it is not already assigned to another range. + +If the IP-based candidate does not reflect the actual RangeId, the mapping can be corrected manually. Once established, MAC-based identification remains authoritative and independent of subsequent IP address changes. + +--- + +#### Range State Extraction + +The interface maintains a local cache of all disciplines, shooters, and start lists known by the ShootMaster server. It is refreshed periodically to reflect changes in the underlying data. + +After receiving a packet and determining the corresponding RangeId, the interface attempts to extract the current state of the range from the packet content. This is done in the following steps: + +The packet content is scanned for occurrences of all known discipline names, shooter names, and start list names. Each occurrence is recorded as a candidate for the current range state, including the position of the match within the packet. This results in a set of candidates for each category (discipline, shooter, start list) that may be present in the packet. + +If multiple shooters share the same name, they are grouped together as a single candidate, no longer being identified by a unique id, but instead being identified by their shared name. This is because the packet does not contain any unique identifiers for these entities, and it cannot be determined which of the shooters with the same name is actually present in the range. + +A first filtering step is applied to the candidate set. It removes all candidates that are completely contained within another candidate of the same type. This is because the string values are surrounded by binary data that, in most cases, does not represent valid strings. Therefore, it can be assumed that the longest match is likely the correct one, as it matches the entire string value, while shorter matches may be partial matches of the same value. + +For example, if the packet contains the string "LG 40", it may match both the discipline "LG 40" and the discipline "LG". In this case, the candidate for "LG" would be removed, as it is fully contained within the longer match "LG 40". + +In the later extraction steps, further logical filtering is applied to the candidate set based on the identified entities. A candidate is only accepted as the identified entity if it is the only remaining candidate for that entity type after all filtering steps. This means that if multiple candidates remain for a category, no identification is made for that category, as it cannot be determined which candidate is correct. + +After each identification, all candidates that overlap with the identified candidate are removed, irrespective of their type. This is because the identified candidate is assumed to be correct, and any overlapping candidates are likely to be false positives. + +Based on empirical observation, each relevant textual value (such as a discipline name, start list name, or shooter name) appears only once within a packet. The value is surrounded by binary data that does not represent additional valid textual content. + +Based on this observation, overlapping candidates are considered mutually exclusive. If a candidate is accepted as correct, any other candidate whose match overlaps with the accepted candidate is assumed to be a false positive and is removed from further consideration. + +##### Start List extraction + +After extracting the candidates in the previous step, the system checks the number of remaining candidates for the start list. If there is more than one candidate, identification is skipped and will be attempted again after attempting to identify the discipline and shooter. + +If there is exactly one candidate, it is accepted as the identified start list. The identified candidate gets removed from the candidate set for the next steps, as well as any candidate that fully or partially overlaps with it. This is because if a start list is identified, its characters cannot be part of any other candidate. + +If there is no candidate, which is a valid state of the range, the start list is reported as empty. + +##### Discipline Extraction + +After the start list extraction, the system checks the number of remaining candidates for the discipline. + +###### Contextual Filtering + +First, discipline candidates that are unlikely to be correct are filtered based on the identified start list. + +This filtering only needs to be applied on price shooting start lists, as they are the only start lists to define their own, dedicated disciplines that are not used in other contexts. + +- If the identified startlist (or all remaining start list candidates) is of type price shooting: + - All discipline candidates belonging to specializations of other price shootings are removed + - Base disciplines are retained, as they may still be used in price shooting contexts. +- If the identified start list (or all remaining start list candidates) is not of type price shooting: + - All discipline candidates that belong to any price shooting specialization are removed + +If no start list could be identified, this filtering step is skipped, as no contextual information is available to determine which disciplines are unlikely. + +###### Unique Resolution + +If, after contextual filtering, exactly one discipline candidate remains, it is accepted as the identified discipline. + +###### Candidate Combination + +If multiple discipline candidates remain, the system evaluates wether they can be safely combined. + +This combination is only considered, if none of the remaining discipline candidates overlap with candidates of other entity types. If such overlaps exist, no combination logic is applied, as removing candidates could discard important contextual information. + +- If all specialization candidates point to the same specialization and this specialization belongs to the start list (or any of the start list candidates, as identified above), the specialization is selected as the final discipline. This reflects the domain observation, that, while technically possible, it is unlikely for a non-specialized discipline to be used in combination with a price shooting +- Otherwise, the base discipline is selected as the identified discipline. This fallback is considered safe, as specializations only affect presentation attributes such as name, identifier or color. Falling back to the base discipline therefore does not result in a loss of critical information + +###### Empty States + +If there is no discipline candidate, which is a possible - but invalid - state of the range, the discipline is reported as empty. This can happen, for example, if the range uses a discipline that is not known to the ShootMaster server, which is possible as the range maintains its own discipline list that can be different from the one on the server. + +##### Shooter Extraction + +After the discipline extraction, the system checks the number of remaining candidates for the shooter. For shooter candidates, no special conditions apply. + +If there is exactly one remaining candidate, it is accepted as the identified shooter. + +If there is no candidate, the range gets classified as empty, which is the normal state of a range after startup or when no shooter is assigned to the range. + +Because candidate elimination is applied after each identification, the extraction of one entity can lead to the successful identification of another entity that was previously ambiguous. Because of this, the extraction process is executed twice, allowing the system to retry extraction after further candidates have been removed in the previous iteration. During this retry, the system does not override any previous identifications. It does not need to be executed more than twice, as after two iterations, no further candidates can be removed based on the identified entities, and therefore no new identification can be made. + +## Known Limitations + +Due to the heuristic and text-based nature of the extraction algorithm, the interface has several inherent limitations. + +### Name Collisions + +The extraction process relies entirely on textual matching and does not use unique identifiers. As a result, name collisions cannot always be resolved reliably. + +- **Within the same entity type** + Multiple disciplines or start lists sharing the same name cannot be uniquely identified and are excluded from matching. + Multiple shooters sharing the same name are grouped and identified only by name. + +- **Across entity types** + If a discipline, start list, or shooter share the same name, the algorithm may not be able to determine the correct entity type. + +### Reserved or Common Keywords + +The packet contains fixed textual keywords (e.g., round names such as *Wertung* or *Probe*). If a discipline or start list uses such a keyword as its name, false positives may occur. + +### Out-of-Sync Discipline Lists + +The range maintains its own discipline list, which must be synchronized with the ShootMaster server. If the range uses a discipline that is not present in the server-side cache, it cannot be detected by this interface. + +### Assumption of Single Occurrence + +The algorithm assumes that each relevant textual value appears only once per packet. If future protocol versions introduce multiple occurrences of the same value, overlapping-candidate elimination may produce incorrect results. + +### Heuristic Matching + +Containment-based filtering and longest-match selection are heuristic strategies. In rare edge cases involving closely related names, this may result in incorrect identification. + +### Protocol and Encoding Changes + +The implementation assumes that relevant data is embedded as human-readable Windows-1252 encoded strings within the packet. Significant protocol or encoding changes may prevent extraction entirely. + +Despite these limitations, the approach has proven stable across multiple ShootMaster releases and real-world installations since 2022, supporting all ShootMaster releases after 4.9.7a. diff --git a/ranges/multicast/docker-compose.yaml b/ranges/multicast/docker-compose.yaml index ac2f5b12..d294c4a3 100644 --- a/ranges/multicast/docker-compose.yaml +++ b/ranges/multicast/docker-compose.yaml @@ -20,6 +20,7 @@ services: - local-db=../../database/local - range-types=../types - logger=../../logger + - streams=../../streams networks: - displaycontroller depends_on: diff --git a/ranges/multicast/package-lock.json b/ranges/multicast/package-lock.json index 522c411d..1e9c31d8 100644 --- a/ranges/multicast/package-lock.json +++ b/ranges/multicast/package-lock.json @@ -9,15 +9,18 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/ahocorasick": "^1.0.0", + "ahocorasick": "^1.0.2", "amqplib": "^0.10.9", "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3" + "dc-streams": "file:../../streams", + "dotenv": "^17.3.1" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -27,15 +30,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -44,21 +47,35 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, + "../../streams": { + "name": "dc-streams", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.9", + "dc-logger": "file:../logger" + }, + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/node": "^25.5.0", + "typescript": "~5.9.3" + } + }, "../types": { "name": "dc-ranges-types", "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -103,6 +120,12 @@ "node": ">= 8" } }, + "node_modules/@types/ahocorasick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/ahocorasick/-/ahocorasick-1.0.0.tgz", + "integrity": "sha512-FNLE6B+9Px4LfRX2bZHSSTFa8Du8RznF5fNX915Ovf+K+g0SljmcmO5u7SH+c3LmeCZwP03NgMb7p82c0+Vbrg==", + "license": "MIT" + }, "node_modules/@types/amqplib": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz", @@ -114,15 +137,21 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, + "node_modules/ahocorasick": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ahocorasick/-/ahocorasick-1.0.2.tgz", + "integrity": "sha512-hCOfMzbFx5IDutmWLAt6MZwOUjIfSM9G9FyVxytmE4Rs/5YDPWQrD/+IR1w+FweD9H2oOZEnv36TmkjhNURBVA==", + "license": "MIT" + }, "node_modules/amqplib": { "version": "0.10.9", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", @@ -239,6 +268,10 @@ "resolved": "../types", "link": true }, + "node_modules/dc-streams": { + "resolved": "../../streams", + "link": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -253,9 +286,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -667,9 +700,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/ranges/multicast/package.json b/ranges/multicast/package.json index abf1f786..abbdc39c 100644 --- a/ranges/multicast/package.json +++ b/ranges/multicast/package.json @@ -11,15 +11,18 @@ "description": "", "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" }, "dependencies": { + "@types/ahocorasick": "^1.0.0", + "ahocorasick": "^1.0.2", "amqplib": "^0.10.9", "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", - "dotenv": "^17.2.3" + "dc-streams": "file:../../streams", + "dotenv": "^17.3.1" } } diff --git a/ranges/multicast/proxy/package-lock.json b/ranges/multicast/proxy/package-lock.json index d8934c71..0f27ade8 100644 --- a/ranges/multicast/proxy/package-lock.json +++ b/ranges/multicast/proxy/package-lock.json @@ -12,12 +12,12 @@ "@network-utils/arp-lookup": "^2.1.0", "amqplib": "^0.10.9", "dc-logger": "file:../../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "iconv-lite": "^0.7.2" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -26,12 +26,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -55,13 +55,13 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/amqplib": { @@ -88,9 +88,9 @@ "link": true }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -148,9 +148,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/ranges/multicast/proxy/package.json b/ranges/multicast/proxy/package.json index 78a50a98..3c34083e 100644 --- a/ranges/multicast/proxy/package.json +++ b/ranges/multicast/proxy/package.json @@ -13,12 +13,12 @@ "@network-utils/arp-lookup": "^2.1.0", "amqplib": "^0.10.9", "dc-logger": "file:../../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "iconv-lite": "^0.7.2" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } } diff --git a/ranges/multicast/proxy/src/index.ts b/ranges/multicast/proxy/src/index.ts index 7ed5548e..5fc3d5ad 100644 --- a/ranges/multicast/proxy/src/index.ts +++ b/ranges/multicast/proxy/src/index.ts @@ -17,17 +17,15 @@ const MESSAGE_MIN_LENGTH = parseInt(process.env.MULTICAST_MSG_MIN_LENGTH); async function main() { const connection = await amqp.connect("amqp://localhost"); const channel = await connection.createChannel(); - await channel.assertQueue("ranges.multicast.proxy", { + await channel.assertExchange("ranges.multicast.proxy", "fanout", { durable: false, - autoDelete: true, - messageTtl: 30000 }); const client = createSocket("udp4"); client.on("listening", function () { client.setMulticastTTL(128); try { - client.addMembership("2224.0.0.1"); + client.addMembership("224.0.0.1"); } catch (e) { } const address = client.address(); @@ -52,10 +50,10 @@ async function main() { const proxiedMessage: RangeProxyType = { ip: remote.address, mac: senderMac, - message: Buffer.from(messageStr).toString("base64") + message: messageStr, } logger.debug(`Received message from ${remote.address}`); - channel.sendToQueue("ranges.multicast.proxy", Buffer.from(JSON.stringify(proxiedMessage))); + channel.publish("ranges.multicast.proxy", "", Buffer.from(JSON.stringify(proxiedMessage))); }); client.on("error", function (error) { logger.error(error); diff --git a/ranges/multicast/src/cache/disciplines.ts b/ranges/multicast/src/cache/disciplines.ts new file mode 100644 index 00000000..f4b77e08 --- /dev/null +++ b/ranges/multicast/src/cache/disciplines.ts @@ -0,0 +1,79 @@ +import { isDiscipline, isOverrideDiscipline } from "dc-ranges-types"; +import { CandidateBaseDiscipline, CandidateDiscipline, CandidateOverrideDiscipline, Matcher } from "../types"; +import { LocalClient } from "dc-db-local"; +import { createMatcher } from "./matcher"; +import { logger } from "dc-logger"; + +export let potentialDisciplines: Matcher = createMatcher(new Map()); + +export async function updatePotentialDisciplines(localClient: LocalClient) { + const disciplines = new Map(); + + const disciplineRoundIdMap = new Map(); + const disciplineData = await localClient.cache.findMany({ + where: { + type: "discipline" + } + }); + for (const disciplineDb of disciplineData) { + if (!disciplineDb.value) { + continue; + } + const discipline = disciplineDb.value; + if (!isDiscipline(discipline)) { + continue; + } + const roundId = discipline.rounds.findIndex(round => round !== null); + if (roundId < 0) { + continue; + } + + disciplineRoundIdMap.set(discipline.id, roundId); + + let entry = disciplines.get(discipline.name); + if (!entry) { + entry = []; + disciplines.set(discipline.name, entry); + } + + const candidateDiscipline: CandidateBaseDiscipline = { + disciplineId: discipline.id, + roundId: roundId + } + entry.push(candidateDiscipline); + } + + const overrideDisciplineData = await localClient.cache.findMany({ + where: { + type: "overrideDiscipline" + } + }); + for (const overrideDisciplineDb of overrideDisciplineData) { + if (!overrideDisciplineDb.value) { + continue; + } + const overrideDiscipline = overrideDisciplineDb.value; + if (!isOverrideDiscipline(overrideDiscipline)) { + continue; + } + const roundId = disciplineRoundIdMap.get(overrideDiscipline.disciplineId); + if (roundId === undefined) { + continue; + } + + let entry = disciplines.get(overrideDiscipline.name); + if (!entry) { + entry = []; + disciplines.set(overrideDiscipline.name, entry); + } + + const candidateDiscipline: CandidateOverrideDiscipline = { + disciplineId: overrideDiscipline.disciplineId, + roundId: roundId, + overrideId: overrideDiscipline.id, + startListId: overrideDiscipline.startListId + } + entry.push(candidateDiscipline); + } + potentialDisciplines = createMatcher(disciplines); +} diff --git a/ranges/multicast/src/updater.ts b/ranges/multicast/src/cache/index.ts similarity index 61% rename from ranges/multicast/src/updater.ts rename to ranges/multicast/src/cache/index.ts index c97357da..924a2ec0 100644 --- a/ranges/multicast/src/updater.ts +++ b/ranges/multicast/src/cache/index.ts @@ -1,10 +1,9 @@ -import { updateDisciplines } from "./discipline"; -import { updateStartList } from "./startList"; -import { updateShooters } from "./shooter"; import { createLocalClient } from "dc-db-local"; import { logger } from "dc-logger"; -import { updateOverrides } from "./discipline/overrides"; import dotenv from "dotenv"; +import { updatePotentialDisciplines } from "./disciplines"; +import { updatePotentialShooters } from "./shooters"; +import { updatePotentialStartLists } from "./startlists"; dotenv.config({ quiet: true }); const client = createLocalClient(); @@ -18,10 +17,9 @@ const refreshInterval = parseInt(process.env.CACHE_REFRESH_INTERVAL); async function update() { logger.info("Updating caches"); await Promise.all([ - updateDisciplines(client), - updateOverrides(client), - updateStartList(client), - updateShooters(client) + updatePotentialDisciplines(client), + updatePotentialShooters(client), + updatePotentialStartLists(client), ]); setTimeout(update, refreshInterval); } diff --git a/ranges/multicast/src/cache/matcher.ts b/ranges/multicast/src/cache/matcher.ts new file mode 100644 index 00000000..ecfcd105 --- /dev/null +++ b/ranges/multicast/src/cache/matcher.ts @@ -0,0 +1,11 @@ +import AhoCorasick from "ahocorasick"; +import { Matcher } from "../types"; + +export function createMatcher(candidates: ReadonlyMap): Matcher { + const needles = Array.from(candidates.keys()); + const matcher = new AhoCorasick(needles); + return { + matcher, + candidates + }; +} \ No newline at end of file diff --git a/ranges/multicast/src/cache/shooters.ts b/ranges/multicast/src/cache/shooters.ts new file mode 100644 index 00000000..539d2a16 --- /dev/null +++ b/ranges/multicast/src/cache/shooters.ts @@ -0,0 +1,45 @@ +import { InternalShooter, isShooter } from "dc-ranges-types"; +import { Matcher } from "../types"; +import { LocalClient } from "dc-db-local"; +import { createMatcher } from "./matcher"; + +export let potentialShooters: Matcher = createMatcher(new Map()); + +export async function updatePotentialShooters(localClient: LocalClient) { + const shooters: Map = new Map(); + + const shooterData = await localClient.cache.findMany({ + where: { + type: "shooter" + } + }); + for (const shooterDb of shooterData) { + if (!shooterDb.value) { + continue; + } + const shooter = shooterDb.value; + if (!isShooter(shooter) || !shooter.id) { + continue; + } + const name = `${shooter.lastName}, ${shooter.firstName}`; + const existing = shooters.get(name); + if (!existing) { + shooters.set(name, { type: "byId", id: shooter.id }); + continue; + } + if (typeof existing === "number") { + shooters.set(name, { + type: "byName", + firstName: shooter.firstName, + lastName: shooter.lastName + }); + } + } + + const normalizedShooters = new Map(); + for (const [name, shooter] of shooters.entries()) { + normalizedShooters.set(name, [shooter]); + } + + potentialShooters = createMatcher(normalizedShooters); +} \ No newline at end of file diff --git a/ranges/multicast/src/cache/startlists.ts b/ranges/multicast/src/cache/startlists.ts new file mode 100644 index 00000000..efee1714 --- /dev/null +++ b/ranges/multicast/src/cache/startlists.ts @@ -0,0 +1,58 @@ +import { Index, isOverrideDiscipline, isStartList } from "dc-ranges-types"; +import { CandidateStartList, Matcher } from "../types"; +import { LocalClient } from "dc-db-local"; +import { createMatcher } from "./matcher"; + +export let potentialStartLists: Matcher = createMatcher(new Map()); + +export async function updatePotentialStartLists(localClient: LocalClient) { + const startLists: Map = new Map(); + + const overrideStartListIds = await getStartListOverrideSet(localClient); + + const startListData = await localClient.cache.findMany({ + where: { + type: "startList" + } + }); + for (const startListDb of startListData) { + if (!startListDb.value) { + continue; + } + const startList = startListDb.value; + if (!isStartList(startList)) { + continue; + } + let entry = startLists.get(startList.name); + if (!entry) { + entry = []; + startLists.set(startList.name, entry); + } + entry.push({ + id: startList.id, + hasOverrideDisciplines: overrideStartListIds.has(startList.id) + }); + } + + potentialStartLists = createMatcher(startLists); +} + +async function getStartListOverrideSet(localClient: LocalClient): Promise> { + const overrideSet = new Set(); + const overrides = await localClient.cache.findMany({ + where: { + type: "overrideDiscipline" + } + }); + for (const override of overrides) { + if (!override.value) { + continue; + } + const overrideData = override.value; + if (!isOverrideDiscipline(overrideData)) { + continue; + } + overrideSet.add(overrideData.startListId); + } + return overrideSet; +} \ No newline at end of file diff --git a/ranges/multicast/src/discipline/index.ts b/ranges/multicast/src/discipline/index.ts deleted file mode 100644 index f8019de0..00000000 --- a/ranges/multicast/src/discipline/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { LocalClient } from "dc-db-local"; -import { isDiscipline, InternalDiscipline } from "dc-ranges-types"; -import { getOverrideDiscipline } from "./overrides"; -import { mergeMaps } from "dc-ranges-types"; - -const matchDiscipline = new Map(); - -export async function updateDisciplines(client: LocalClient) { - const disciplines = await client.cache.findMany({ - where: { - type: "discipline" - } - }); - const disciplineTempMap = new Map(); - for (const discipline of disciplines) { - if (!isDiscipline(discipline.value)) { - continue; - } - disciplineTempMap.set(`${discipline.value.name}\0`, { - disciplineId: Number(discipline.key), - roundId: discipline.value.rounds.findIndex(round => round !== null) - }); - } - mergeMaps(matchDiscipline, disciplineTempMap); -} - -export function getDiscipline(startListId: number | null, message: string): InternalDiscipline | null { - if (startListId !== null) { - const overrideDiscipline = getOverrideDiscipline(startListId, message); - if (overrideDiscipline) { - return overrideDiscipline; - } - } - const keys = Array.from(matchDiscipline.keys()).sort((a, b) => b.length - a.length);// sort by length descending - for (const name of keys) { - if (message.includes(name)) { - return structuredClone(matchDiscipline.get(name) ?? null); - } - } - return null; -} \ No newline at end of file diff --git a/ranges/multicast/src/discipline/overrides/index.ts b/ranges/multicast/src/discipline/overrides/index.ts deleted file mode 100644 index 2bab0990..00000000 --- a/ranges/multicast/src/discipline/overrides/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { LocalClient } from "dc-db-local"; -import { isDiscipline, InternalDiscipline, isOverrideDiscipline, mergeMaps } from "dc-ranges-types"; - -const overrideDisciplines = new Map>(); - -export async function updateOverrides(client: LocalClient) { - const overrideDisciplinesData = await client.cache.findMany({ - where: { - type: "overrideDiscipline" - } - }); - const overrideDisciplinesTempMap = new Map>(); - for (const overrideDisciplineDb of overrideDisciplinesData) { - if (!isOverrideDiscipline(overrideDisciplineDb.value)) { - continue; - } - const overrideDiscipline = overrideDisciplineDb.value; - const originalDiscipline = await client.cache.findUnique({ - where: { - type_key: { - type: "discipline", - key: overrideDiscipline.disciplineId - } - } - }); - if (!originalDiscipline || !isDiscipline(originalDiscipline.value)) { - continue; - } - const minRoundId = originalDiscipline.value.rounds.findIndex(round => round !== null); - const startListId = Number(overrideDiscipline.startListId); - if (!overrideDisciplinesTempMap.has(startListId)) { - overrideDisciplinesTempMap.set(startListId, new Map()); - } - overrideDisciplinesTempMap.get(startListId)!.set(overrideDiscipline.name, { - overrideId: Number(overrideDiscipline.id), - roundId: minRoundId - }); - } - mergeMaps(overrideDisciplines, overrideDisciplinesTempMap); -} - -export function getOverrideDiscipline(startListId: number, message: string): InternalDiscipline | null { - const disciplines = overrideDisciplines.get(startListId); - if (!disciplines) { - return null; - } - const keys = Array.from(disciplines.keys()).sort((a, b) => b.length - a.length);// sort by length descending - for (const name of keys) { - if (message.includes(name)) { - return structuredClone(disciplines.get(name) ?? null); - } - } - return null; -} \ No newline at end of file diff --git a/ranges/multicast/src/index.ts b/ranges/multicast/src/index.ts index bf346c51..c799f544 100644 --- a/ranges/multicast/src/index.ts +++ b/ranges/multicast/src/index.ts @@ -1,64 +1,31 @@ -import "./updater"; +import amqp from 'amqplib'; +import { RabbitMqReceiver, RabbitMqWriter } from 'dc-streams'; import { isRangeProxy } from "../proxy/src/types"; -import amqp from "amqplib"; -import { getStartList } from "./startList"; -import { getDiscipline } from "./discipline"; -import { getShooters } from "./shooter"; -import { getRangeId } from "./rangeId"; -import { createLocalClient } from "dc-db-local"; -import { InternalRange } from "dc-ranges-types"; -import { logger } from "dc-logger"; - -const prismaClient = createLocalClient(); +import { RangeIdentifier } from './streams/rangeId'; +import { createLocalClient } from 'dc-db-local'; +import { CandidateExtractor } from './streams/candidates'; +import { RangeDataConverter } from './streams/rangeDataConverter'; +import { InternalRange } from 'dc-ranges-types'; +import "./cache"; +import { ConstraintSolver } from './streams/constraintSolver'; +import { ContainedCandidatesFilter } from './streams/overlapFilter'; async function main() { + const localClient = await createLocalClient(); const connection = await amqp.connect("amqp://rabbitmq"); - const channel = await connection.createChannel(); - await channel.assertQueue("ranges.multicast.proxy", { - durable: false, - autoDelete: true, - messageTtl: 30000 - }); - await channel.assertExchange("ranges.multicast", "fanout", { - durable: false, - }); - channel.consume("ranges.multicast.proxy", async (msg) => { - if (msg === null) { - return; - } - const message = JSON.parse(msg.content.toString()); - if (!isRangeProxy(message)) { - return; - } - const multicastMessage = Buffer.from(message.message, "base64").toString(); - const rangeData = await getRangeData(multicastMessage, message.mac, message.ip); - if (rangeData === null) { - logger.error("Failed to parse range data for " + message.ip); - return; - } - logger.info(`Parsed data for range ${rangeData.rangeId} (${message.ip})`); - channel.publish("ranges.multicast", "", Buffer.from(JSON.stringify(rangeData))); - channel.ack(msg); - }); -} -async function getRangeData(message: string, rangeMac: string, rangeIp: string): Promise { - const rangeId = await getRangeId(prismaClient, rangeMac, rangeIp); - if (rangeId === null) { - return null; - } - const startList = getStartList(message); - const discipline = getDiscipline(startList, message); - const shooter = getShooters(message); - return { - rangeId: rangeId, - startListId: startList, - shooter: shooter, - discipline: discipline, - hits: [], - source: "multicast", - ttl: 20000 - } + const receiver = new RabbitMqReceiver(connection, ["ranges.multicast.proxy"], isRangeProxy); + + const rangeIdentifier = new RangeIdentifier(localClient); + const candidateExtractor = new CandidateExtractor(); + + const overlapFilter = new ContainedCandidatesFilter(); + const constraintSolver = new ConstraintSolver(); + + const rangeDataConverter = new RangeDataConverter(); + const sender = new RabbitMqWriter(connection, ["ranges.multicast"], (range) => range.rangeId.toString()); + + receiver.pipe(rangeIdentifier).pipe(candidateExtractor).pipe(overlapFilter).pipe(constraintSolver).pipe(rangeDataConverter).pipe(sender); } -main(); +main() \ No newline at end of file diff --git a/ranges/multicast/src/rangeId/index.ts b/ranges/multicast/src/rangeId/index.ts deleted file mode 100644 index 32acc6a3..00000000 --- a/ranges/multicast/src/rangeId/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { LocalClient } from "dc-db-local"; - - -export async function getRangeId(prismaClient: LocalClient, rangeMac: string, rangeIp: string): Promise { - const range = await prismaClient.knownRanges.findUnique({ - where: { - macAddress: rangeMac, - } - }); - if (!range) { - const rangeIdStr = rangeIp.split(".")?.pop(); - if (!rangeIdStr) { - throw new Error(`Could not resolve range id for ip ${rangeIp}`); - } - let rangeId = parseInt(rangeIdStr); - if (isNaN(rangeId)) { - throw new Error(`Could not resolve range id for ip ${rangeIp}`); - } - const exists = await prismaClient.knownRanges.findUnique({ - where: { - rangeId: rangeId - } - }); - if (exists) { - return null; - } - await prismaClient.knownRanges.create({ - data: { - rangeId: rangeId, - macAddress: rangeMac, - lastIp: rangeIp - } - }); - return rangeId; - } - if (range.lastIp !== rangeIp) { - await prismaClient.knownRanges.update({ - where: { - macAddress: rangeMac, - }, - data: { - lastIp: rangeIp - } - }); - } - return range.rangeId; -} \ No newline at end of file diff --git a/ranges/multicast/src/shooter/index.ts b/ranges/multicast/src/shooter/index.ts deleted file mode 100644 index c6a7f534..00000000 --- a/ranges/multicast/src/shooter/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { LocalClient } from "dc-db-local"; -import { isShooter, InternalShooter, mergeMaps } from "dc-ranges-types" - -const matchShooter = new Map(); - -export async function updateShooters(client: LocalClient) { - const shooters = await client.cache.findMany({ - where: { - type: "shooter" - } - }); - const shooterTempMap = new Map(); - for (const shooter of shooters) { - if (!isShooter(shooter.value)) { - continue; - } - if (!shooter.value.id) { - continue; - } - if (shooterTempMap.has(`${shooter.value.lastName}, ${shooter.value.firstName}`)) { - shooterTempMap.set(`${shooter.value.lastName}, ${shooter.value.firstName}`, { - firstName: shooter.value.firstName, - lastName: shooter.value.lastName - }); - } else { - shooterTempMap.set(`${shooter.value.lastName}, ${shooter.value.firstName}`, shooter.value.id); - } - } - mergeMaps(matchShooter, shooterTempMap); -} - -export function getShooters(message: string): InternalShooter | null { - const names = Array.from(matchShooter.keys()).sort((a, b) => b.length - a.length); - for (const name of names) { - if (message.includes(name)) { - return structuredClone(matchShooter.get(name) ?? null); - } - } - return null; -} - diff --git a/ranges/multicast/src/startList/index.ts b/ranges/multicast/src/startList/index.ts deleted file mode 100644 index 70a5e3cb..00000000 --- a/ranges/multicast/src/startList/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { LocalClient } from "dc-db-local"; -import { isStartList, mergeMaps } from "dc-ranges-types"; - -const matchStartLists = new Map(); - -export async function updateStartList(client: LocalClient) { - const startList = await client.cache.findMany({ - where: { - type: "startList" - } - }); - const startListTempMap = new Map(); - for (const list of startList) { - if (!isStartList(list.value)) { - continue; - } - if (!list.value.name) { - continue; - } - startListTempMap.set(list.value.name, Number(list.key)); - } - mergeMaps(matchStartLists, startListTempMap); -} - -export function getStartList(message: string): number | null { - const names = Array.from(matchStartLists.keys()) - .sort((a, b) => b.length - a.length); // longest first - - for (const name of names) { - if (message.includes(name)) { - return structuredClone(matchStartLists.get(name) ?? null); - } - } - - return null; -} \ No newline at end of file diff --git a/ranges/multicast/src/streams/candidates.ts b/ranges/multicast/src/streams/candidates.ts new file mode 100644 index 00000000..9d971743 --- /dev/null +++ b/ranges/multicast/src/streams/candidates.ts @@ -0,0 +1,59 @@ +import { TypedTransform } from "dc-streams"; +import { IdentifiedRange } from "./rangeId"; +import { Candidate, Matcher, PacketCandidates } from "../types"; +import { TransformCallback } from "node:stream"; +import { potentialDisciplines } from "../cache/disciplines"; +import { potentialStartLists } from "../cache/startlists"; +import { potentialShooters } from "../cache/shooters"; +import { logger } from "dc-logger"; + +export class CandidateExtractor extends TypedTransform { + + private findCandidates(packet: string, matcher: Matcher): Candidate[] { + const matches = matcher.matcher.search(packet); + const candidates: Candidate[] = []; + for (const [position, needles] of matches) { + + for (const needle of needles) { + const data = matcher.candidates.get(needle); + + if (!data) { + continue; + } + + // Position is the index of the end of the match, we need to calculate the start index. + const start = position - needle.length; + candidates.push(...data.map(candidateData => ({ + start, + end: position, + data: candidateData, + match: needle + }))); + } + } + return candidates; + } + + _transform(chunk: IdentifiedRange, encoding: BufferEncoding, callback: TransformCallback): void { + logger.info(`Received packet from range ${chunk.id}`); + logger.debug(`Packet content: ${chunk.packet}`); + + const disciplineCandidates = this.findCandidates(chunk.packet, potentialDisciplines); + const startListCandidates = this.findCandidates(chunk.packet, potentialStartLists); + const shooterCandidates = this.findCandidates(chunk.packet, potentialShooters); + + logger.debug(`Found ${disciplineCandidates.length} discipline candidates: ${disciplineCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Found ${startListCandidates.length} start list candidates: ${startListCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Found ${shooterCandidates.length} shooter candidates: ${shooterCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + + const candidates: PacketCandidates = { + id: chunk.id, + disciplineCandidates, + startListCandidates, + shooterCandidates + }; + + this.push(candidates); + callback(); + } +} \ No newline at end of file diff --git a/ranges/multicast/src/streams/constraintSolver.ts b/ranges/multicast/src/streams/constraintSolver.ts new file mode 100644 index 00000000..4238fcad --- /dev/null +++ b/ranges/multicast/src/streams/constraintSolver.ts @@ -0,0 +1,178 @@ +import { TypedTransform } from "dc-streams"; +import { Candidate, CandidateDiscipline, CandidateOverrideDiscipline, CandidateStartList, isOverride, PacketCandidates, ResolvedPacketCandidates } from "../types"; +import { TransformCallback } from "node:stream"; +import { StartList } from "dc-ranges-types"; +import { logger } from "dc-logger"; + +export class ConstraintSolver extends TypedTransform { + private removeOverlaps(candidates: Candidate[], toRemove: Candidate): Candidate[] { + // Remove candidates that overlap with toRemove + return candidates.filter(candidate => candidate.end <= toRemove.start || candidate.start >= toRemove.end); + } + + private hasAnyOverlaps(candidates: PacketCandidates, b: Candidate): boolean { + return candidates.disciplineCandidates.some(candidate => !(candidate.end <= b.start || candidate.start >= b.end)) || + candidates.startListCandidates.some(candidate => !(candidate.end <= b.start || candidate.start >= b.end)) || + candidates.shooterCandidates.some(candidate => !(candidate.end <= b.start || candidate.start >= b.end)); + } + + private resolveCandidate( + candidates: ResolvedPacketCandidates, + key: K, + candidate: Candidate, + ): ResolvedPacketCandidates { + logger.debug(`Resolving ${key} to ${candidate.match} based on candidate at position (${candidate.start},${candidate.end})`); + // print remaining candidates after filtering + const filteredDisciplineCandidates = key === "discipline" ? [] : this.removeOverlaps(candidates.disciplineCandidates, candidate); + const filteredStartListCandidates = key === "startList" ? [] : this.removeOverlaps(candidates.startListCandidates, candidate); + const filteredShooterCandidates = key === "shooter" ? [] : this.removeOverlaps(candidates.shooterCandidates, candidate); + logger.debug(`Remaining discipline candidates after resolving ${key}: ${filteredDisciplineCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Remaining start list candidates after resolving ${key}: ${filteredStartListCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Remaining shooter candidates after resolving ${key}: ${filteredShooterCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + return { + ...candidates, + [key]: candidate.data, + disciplineCandidates: filteredDisciplineCandidates, + startListCandidates: filteredStartListCandidates, + shooterCandidates: filteredShooterCandidates + } as ResolvedPacketCandidates; + } + + private resolveStartList(candidates: ResolvedPacketCandidates): ResolvedPacketCandidates { + if (candidates.startListCandidates.length === 1) { + //logger.debug(`Only one start list candidate remains, resolving to ${candidates.startListCandidates[0].match}`); + const candidate = candidates.startListCandidates[0]; + return this.resolveCandidate(candidates, "startList", candidate); + } + // if all remaining start list candidates point to the same start list (and they have no overlaps with candidates of other types), this start list is selected as the identified start list, as it is likely that the different candidates are just different parts of the same start list name. This reflects the domain observation, that, while technically possible, it is unlikely for a range to contain multiple different start lists, as this would usually indicate a misconfiguration of the range. + + const candidatesWithoutStartListCandidates = { + ...candidates, + startListCandidates: [] // This should ignore any start list candidates in the overlap check, as we want to check if any start list candidate overlaps with candidates of other types. + }; + const startListIds = new Set(candidates.startListCandidates.map(candidate => candidate.data.id)); + if (startListIds.size === 1 && !candidates.startListCandidates.some(candidate => this.hasAnyOverlaps(candidatesWithoutStartListCandidates, candidate))) { + const candidate = candidates.startListCandidates[0]; + return this.resolveCandidate(candidates, "startList", candidate); + } + return candidates; + } + + private filterDisciplinesByStartList(candidates: ResolvedPacketCandidates): Candidate[] { + const startLists = candidates.startListCandidates.map(candidate => candidate.data); + if (candidates.startList) { + startLists.push(candidates.startList); + } + const disciplineCandidates = candidates.disciplineCandidates; + if (startLists.length === 0) { + return disciplineCandidates; + } + + const overrideStartListIds = startLists + .filter(candidate => candidate.hasOverrideDisciplines) + .map(candidate => candidate.id); + const overrideStartListIdsSet = new Set(overrideStartListIds); + + // Check if all identified start list candidates are price shooting start lists + if (overrideStartListIds.length === startLists.length) { + // If all identified start list candidates are price shooting start lists, only retain discipline candidates that belong to specializations of these price shootings or base disciplines, as these are the only disciplines that can be used in price shooting contexts + return disciplineCandidates.filter(candidate => { + const discipline = candidate.data; + return !isOverride(discipline) || overrideStartListIdsSet.has(discipline.startListId); + }); + } + + if (overrideStartListIds.length === 0) { + // If none of the identified start list candidates are price shooting start lists, only retain discipline candidates that do not belong to any price shooting specialization, as these are the only disciplines that can be used in non-price shooting contexts + return disciplineCandidates.filter(candidate => { + const discipline = candidate.data; + return !isOverride(discipline) // Only specialization disciplines have a startListId + }); + } + // If there is a mix of price shooting and non-price shooting start list candidates, no discipline candidates can be safely removed, as it is unclear which start list candidate is correct + return disciplineCandidates; + } + + private resolveDiscipline(candidates: ResolvedPacketCandidates): ResolvedPacketCandidates { + if (candidates.discipline) { + return candidates; + } + const disciplineCandidates = this.filterDisciplinesByStartList(candidates); + logger.debug(`After filtering disciplines by start list, ${disciplineCandidates.length} discipline candidates remain: ${disciplineCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + if (disciplineCandidates.length === 1) { + // If, after contextual filtering, exactly one discipline candidate remains, it is accepted as the identified discipline. + const discipline = disciplineCandidates[0]; + return this.resolveCandidate(candidates, "discipline", discipline); + } + // This combination is only considered, if none of the remaining discipline candidates overlap with candidates of other entity types. If such overlaps exist, no combination logic is applied, as removing candidates could discard important contextual information. + const filteredCandidates = { + ...candidates, + disciplineCandidates: [] // This should ignore any discipline candidates in the overlap check, as we want to check if any discipline candidate overlaps with candidates of other types. + } + if (disciplineCandidates.some(candidate => this.hasAnyOverlaps(filteredCandidates, candidate))) { + logger.debug(`Multiple discipline candidates remain, but at least one overlaps with candidates of other types, cannot resolve discipline`); + return filteredCandidates; + } + + // - If all specialization candidates point to the same specialization and this specialization belongs to the start list (or any of the start list candidates, as identified above), the specialization is selected as the final discipline. This reflects the domain observation, that, while technically possible, it is unlikely for a non-specialized discipline to be used in combination with a price shooting + // We do not need to check for start lists here, as the filtering is done in the filterDisciplinesByStartList + const specializationCandidates = disciplineCandidates.filter(candidate => isOverride(candidate.data)) as unknown as Array>; + if (specializationCandidates.length > 0) { + const specializationIds = new Set(specializationCandidates.map(candidate => candidate.data.overrideId)); + if (specializationIds.size === 1) { + logger.debug(`Multiple specialization candidates remain, but they all belong to the same specialization, resolving to ${specializationCandidates[0].match} (${specializationCandidates[0].data.overrideId})`); + const specializationCandidate = specializationCandidates[0]; + return this.resolveCandidate(candidates, "discipline", specializationCandidate); + } + } + + // - Otherwise, the base discipline is selected as the identified discipline. This fallback is considered safe, as specializations only affect presentation attributes such as name, identifier or color. Falling back to the base discipline therefore does not result in a loss of critical information + const baseDisciplineIds = new Set(disciplineCandidates.map(candidate => candidate.data.disciplineId)); + if (baseDisciplineIds.size === 1) { + const baseDisciplineId = baseDisciplineIds.values().next().value; + logger.debug(`Multiple discipline candidates remain, but they all belong to the same base discipline, resolving to ${baseDisciplineId}`); + const baseDisciplineCandidate = disciplineCandidates.find(candidate => !isOverride(candidate.data)) || disciplineCandidates[0]; // If there is no base discipline candidate, which can happen if all discipline candidates are specializations of the same base discipline, just take any candidate, as they all belong to the same base discipline + return this.resolveCandidate(candidates, "discipline", baseDisciplineCandidate); + } + + // If there is no discipline candidate, which is a possible - but invalid - state of the range, the discipline is reported as empty. This can happen, for example, if the range uses a discipline that is not known to the ShootMaster server, which is possible as the range maintains its own discipline list that can be different from the one on the server. + return candidates; + } + + private resolveShooter(candidates: ResolvedPacketCandidates): ResolvedPacketCandidates { + if (candidates.shooterCandidates.length === 1) { + const candidate = candidates.shooterCandidates[0]; + return this.resolveCandidate(candidates, "shooter", candidate); + } + return candidates; + } + + _transform(chunk: PacketCandidates, encoding: BufferEncoding, callback: TransformCallback): void { + let candidates: ResolvedPacketCandidates = { + ...chunk, + discipline: null, + startList: null, + shooter: null + }; + + // First pass + candidates = this.resolveStartList(candidates); + candidates = this.resolveDiscipline(candidates); + candidates = this.resolveShooter(candidates); + + // Second pass to catch any new resolutions that were made possible by the first pass + candidates = this.resolveStartList(candidates); + candidates = this.resolveDiscipline(candidates); + candidates = this.resolveShooter(candidates); + + logger.debug(`After constraint solving, resolved discipline: ${candidates.discipline ? candidates.discipline.disciplineId : "none"}`); + logger.debug(`After constraint solving, resolved start list: ${candidates.startList ? candidates.startList.id : "none"}`); + logger.debug(`After constraint solving, resolved shooter: ${JSON.stringify(candidates.shooter)}`); + logger.debug(`Remaining discipline candidates: ${candidates.disciplineCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Remaining start list candidates: ${candidates.startListCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`Remaining shooter candidates: ${candidates.shooterCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + + this.push(candidates); + callback(); + } +} \ No newline at end of file diff --git a/ranges/multicast/src/streams/longestMatchReducer.ts b/ranges/multicast/src/streams/longestMatchReducer.ts new file mode 100644 index 00000000..80ff6417 --- /dev/null +++ b/ranges/multicast/src/streams/longestMatchReducer.ts @@ -0,0 +1,40 @@ +import { TypedTransform } from "dc-streams"; +import { Candidate, PacketCandidates, ResolvedPacketCandidates } from "../types"; + +export class LongestMatchReducer extends TypedTransform { + private reduceToLongestMatch(candidates: Candidate[]): T | null { + if (candidates.length === 0) { + return null; + } + + let longest = candidates[0]; + let longestLength = longest.end - longest.start; + for (const candidate of candidates) { + const len = candidate.end - candidate.start; + if (len > longestLength) { + longest = candidate; + longestLength = len; + } + } + return longest.data; + } + + _transform(chunk: PacketCandidates, encoding: BufferEncoding, callback: () => void): void { + const discipline = this.reduceToLongestMatch(chunk.disciplineCandidates); + const startList = this.reduceToLongestMatch(chunk.startListCandidates); + const shooter = this.reduceToLongestMatch(chunk.shooterCandidates); + + const reduced: ResolvedPacketCandidates = { + id: chunk.id, + disciplineCandidates: [], + startListCandidates: [], + shooterCandidates: [], + discipline, + startList, + shooter + + }; + this.push(reduced); + callback(); + } +} diff --git a/ranges/multicast/src/streams/overlapFilter.ts b/ranges/multicast/src/streams/overlapFilter.ts new file mode 100644 index 00000000..384b7d12 --- /dev/null +++ b/ranges/multicast/src/streams/overlapFilter.ts @@ -0,0 +1,63 @@ +import { TypedTransform } from "dc-streams"; +import { Candidate, PacketCandidates } from "../types"; +import { logger } from "dc-logger"; + +export class ContainedCandidatesFilter extends TypedTransform { + private filterContainedCandidate(candidates: Candidate[]): Candidate[] { + // This should remove all candidates that are fully covered by another candidate. + // It should not remove candidates that are the same length but different data, as they still add more information. + + const sorted = candidates.slice().sort((a, b) => a.start - b.start || b.end - a.end); + + const filtered: Candidate[] = []; + let lastEnd = -1; + let lastStart = -1; + for (const candidate of sorted) { + if (candidate.end <= lastEnd && !(candidate.start === lastStart && candidate.end === lastEnd)) { + continue; + } + filtered.push(candidate); + lastEnd = Math.max(lastEnd, candidate.end); + lastStart = candidate.start; + } + return filtered; + } + + private filterContainedOverlapsWithDifferentTypes(candidates: Candidate[], otherCandidates: Candidate[]): Candidate[] { + // This should remove candidates that are fully covered by another candidate of a different type, as they likely don't add any information. + // For example, if a discipline candidate is fully covered by a shooter candidate, it likely doesn't add any information, as the shooter candidate already implies the discipline. + // it should not remove candidates that are the same length but different data, as they still add more information. + + const filtered: Candidate[] = []; + for (const candidate of candidates) { + const isCovered = otherCandidates.some(other => other.start <= candidate.start && other.end >= candidate.end && !(other.start === candidate.start && other.end === candidate.end)); + if (!isCovered) { + filtered.push(candidate); + } + } + return filtered; + } + + _transform(chunk: PacketCandidates, encoding: BufferEncoding, callback: () => void): void { + const disciplineCandidates = this.filterContainedCandidate(chunk.disciplineCandidates); + const startListCandidates = this.filterContainedCandidate(chunk.startListCandidates); + const shooterCandidates = this.filterContainedCandidate(chunk.shooterCandidates); + + const filteredDisciplineCandidates = this.filterContainedOverlapsWithDifferentTypes(disciplineCandidates, [...startListCandidates, ...shooterCandidates]); + const filteredShooterCandidates = this.filterContainedOverlapsWithDifferentTypes(shooterCandidates, [...disciplineCandidates, ...startListCandidates]); + const filteredStartListCandidates = this.filterContainedOverlapsWithDifferentTypes(startListCandidates, [...disciplineCandidates, ...shooterCandidates]); + + logger.debug(`After filtering, ${filteredDisciplineCandidates.length} discipline candidates remain: ${filteredDisciplineCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`After filtering, ${filteredStartListCandidates.length} start list candidates remain: ${filteredStartListCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + logger.debug(`After filtering, ${filteredShooterCandidates.length} shooter candidates remain: ${filteredShooterCandidates.map(c => `${c.match} (${c.start},${c.end})`).join(", ")}`); + + const filtered: PacketCandidates = { + id: chunk.id, + disciplineCandidates: filteredDisciplineCandidates, + startListCandidates: filteredStartListCandidates, + shooterCandidates: filteredShooterCandidates + }; + this.push(filtered); + callback(); + } +} \ No newline at end of file diff --git a/ranges/multicast/src/streams/rangeDataConverter.ts b/ranges/multicast/src/streams/rangeDataConverter.ts new file mode 100644 index 00000000..26e8e423 --- /dev/null +++ b/ranges/multicast/src/streams/rangeDataConverter.ts @@ -0,0 +1,52 @@ +import { TypedTransform } from "dc-streams" +import { isOverride, ResolvedPacketCandidates } from "../types"; +import { Index, InternalDiscipline, InternalRange, InternalShooter } from "dc-ranges-types"; + +export class RangeDataConverter extends TypedTransform { + + private resolveShooter(chunk: ResolvedPacketCandidates): InternalShooter | null { + if (chunk.shooterCandidates.length > 0) { + return null; // ambiguous, cannot resolve + } + if (chunk.shooter) { + return chunk.shooter; + } + return { type: "free" }; + + } + + private resolveDiscipline(chunk: ResolvedPacketCandidates): InternalDiscipline | null { + const discipline = chunk.discipline; + if (!discipline) { + return null; + } + if (isOverride(discipline)) { + return { + overrideId: discipline.overrideId, + roundId: discipline.roundId, + }; + } + return { + disciplineId: discipline.disciplineId, + roundId: discipline.roundId, + }; + } + + private resolveStartList(chunk: ResolvedPacketCandidates): Index | null { + return chunk.startList ? chunk.startList.id : null; + } + + _transform(chunk: ResolvedPacketCandidates, encoding: BufferEncoding, callback: () => void): void { + const range: InternalRange = { + rangeId: chunk.id, + discipline: this.resolveDiscipline(chunk), + startListId: this.resolveStartList(chunk), + shooter: this.resolveShooter(chunk), + hits: [], + source: "multicast", + ttl: 20000 + }; + this.push(range); + callback(); + } +} diff --git a/ranges/multicast/src/streams/rangeId.ts b/ranges/multicast/src/streams/rangeId.ts new file mode 100644 index 00000000..bb76d81c --- /dev/null +++ b/ranges/multicast/src/streams/rangeId.ts @@ -0,0 +1,90 @@ +import { TypedTransform } from "dc-streams"; +import { RangeProxyType } from "../../proxy/src/types"; +import { LocalClient } from "dc-db-local"; +import { logger } from "dc-logger"; +import { RangeId } from "dc-ranges-types"; + +export type IdentifiedRange = { + id: RangeId; + packet: string; +} + +export class RangeIdentifier extends TypedTransform { + private readonly prismaClient: LocalClient; + + constructor(prismaClient: LocalClient) { + super(); + this.prismaClient = prismaClient; + } + + async resolveRangeId(rangeMac: string, rangeIp: string): Promise { + const existing = await this.prismaClient.knownRanges.findUnique({ + where: { macAddress: rangeMac } + }); + + if (existing) { + if (existing.lastIp !== rangeIp) { + await this.prismaClient.knownRanges.update({ + where: { macAddress: rangeMac }, + data: { lastIp: rangeIp } + }); + } + + return existing.rangeId; + } + + // MAC not known → derive ID + let rangeId: number | null = null; + + const parts = rangeIp.split("."); + if (parts.length !== 4) { + logger.warn(`Invalid IP ${rangeIp} for range with mac ${rangeMac}`); + } + + const candidate = parseInt(parts[3], 10); + if (isNaN(candidate)) { + logger.warn(`Invalid IP ${rangeIp} for range with mac ${rangeMac}`); + } + + const exists = await this.prismaClient.knownRanges.findUnique({ + where: { rangeId: candidate } + }); + + if (!exists) { + rangeId = candidate; + } else { + logger.warn(`Range ID ${candidate} already exists`); + } + + // Safe insert + const created = await this.prismaClient.knownRanges.upsert({ + where: { macAddress: rangeMac }, + update: { lastIp: rangeIp }, + create: { + macAddress: rangeMac, + lastIp: rangeIp, + rangeId: rangeId + } + }); + + return created.rangeId; + } + + async _transform(value: RangeProxyType, encoding: BufferEncoding, callback: (error?: Error | null, data?: IdentifiedRange) => void): Promise { + try { + const rangeId = await this.resolveRangeId(value.mac, value.ip); + if (!rangeId) { + logger.warn(`Could not resolve range id for range with mac ${value.mac} and ip ${value.ip}, skipping packet`); + } else { + this.push({ + id: rangeId, + packet: value.message + }); + } + callback(); + } catch (error) { + logger.error(`Error while getting range id for range with mac ${value.mac} and ip ${value.ip}: ${error}`); + callback(error as Error); + } + } +} \ No newline at end of file diff --git a/ranges/multicast/src/types.ts b/ranges/multicast/src/types.ts new file mode 100644 index 00000000..20917de9 --- /dev/null +++ b/ranges/multicast/src/types.ts @@ -0,0 +1,48 @@ +import AhoCorasick from "ahocorasick"; +import { Index, InternalShooter, RangeId, UnsignedInteger } from "dc-ranges-types"; + +export type Candidate = { + readonly start: UnsignedInteger, + readonly end: UnsignedInteger, + readonly data: T + readonly match: string; +} + +export type CandidateBaseDiscipline = { + disciplineId: Index; + roundId: Index; +} + +export type CandidateOverrideDiscipline = CandidateBaseDiscipline & { + overrideId: Index; + startListId: Index; +} + +export function isOverride(discipline: CandidateDiscipline): discipline is CandidateOverrideDiscipline { + return "overrideId" in discipline; +} + +export type CandidateDiscipline = CandidateBaseDiscipline | CandidateOverrideDiscipline; + +export type CandidateStartList = { + id: Index; + hasOverrideDisciplines: boolean; +}; + +export type Matcher = { + matcher: AhoCorasick; + candidates: ReadonlyMap; +} + +export type PacketCandidates = { + id: RangeId; + disciplineCandidates: Candidate[]; + startListCandidates: Candidate[]; + shooterCandidates: Candidate[]; +} + +export type ResolvedPacketCandidates = PacketCandidates & { + discipline: CandidateDiscipline | null; + startList: CandidateStartList | null; + shooter: InternalShooter | null; +} \ No newline at end of file diff --git a/ranges/ssmdb2/Dockerfile b/ranges/ssmdb2/Dockerfile index 04d18a4b..bcd45aaa 100644 --- a/ranges/ssmdb2/Dockerfile +++ b/ranges/ssmdb2/Dockerfile @@ -42,10 +42,17 @@ COPY --from=range-types . . RUN npm ci --loglevel=info && \ npm run build; +FROM deps-logger AS deps-streams +WORKDIR /app/streams +COPY --from=streams . . +RUN npm ci --loglevel=info && \ + npm run build; + FROM base AS local-deps WORKDIR /app COPY --from=deps-logger /app/logger /app/logger +COPY --from=deps-streams /app/streams /app/streams COPY --from=deps-local-db /app/database/local /app/database/local COPY --from=deps-ssmdb2 /app/database/ssmdb2 /app/database/ssmdb2 COPY --from=deps-table-watcher /app/database/tableWatcher /app/database/tableWatcher diff --git a/ranges/ssmdb2/docker-compose.yaml b/ranges/ssmdb2/docker-compose.yaml index 5f964289..c6bc71df 100644 --- a/ranges/ssmdb2/docker-compose.yaml +++ b/ranges/ssmdb2/docker-compose.yaml @@ -21,6 +21,7 @@ services: - range-types=../types - table-watcher=../../database/tableWatcher - logger=../../logger + - streams=../../streams networks: - displaycontroller depends_on: diff --git a/ranges/ssmdb2/package-lock.json b/ranges/ssmdb2/package-lock.json index b747c539..f49a8160 100644 --- a/ranges/ssmdb2/package-lock.json +++ b/ranges/ssmdb2/package-lock.json @@ -14,12 +14,13 @@ "dc-db-ssmdb2": "file:../../database/ssmdb2", "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", + "dc-streams": "file:../../streams", "dc-table-watcher": "file:../../database/tableWatcher", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -29,15 +30,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -46,14 +47,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -66,8 +67,8 @@ "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -76,21 +77,35 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, + "../../streams": { + "name": "dc-streams", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.9", + "dc-logger": "file:../logger" + }, + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/node": "^25.5.0", + "typescript": "~5.9.3" + } + }, "../types": { "name": "dc-ranges-types", "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -146,13 +161,13 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/amqplib": { @@ -275,6 +290,10 @@ "resolved": "../types", "link": true }, + "node_modules/dc-streams": { + "resolved": "../../streams", + "link": true + }, "node_modules/dc-table-watcher": { "resolved": "../../database/tableWatcher", "link": true @@ -293,9 +312,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -707,9 +726,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/ranges/ssmdb2/package.json b/ranges/ssmdb2/package.json index 1913f9db..7d9ab517 100644 --- a/ranges/ssmdb2/package.json +++ b/ranges/ssmdb2/package.json @@ -11,7 +11,7 @@ "description": "", "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" }, @@ -21,7 +21,8 @@ "dc-db-ssmdb2": "file:../../database/ssmdb2", "dc-logger": "file:../../logger", "dc-ranges-types": "file:../types", + "dc-streams": "file:../../streams", "dc-table-watcher": "file:../../database/tableWatcher", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" } } diff --git a/ranges/ssmdb2/src/index.ts b/ranges/ssmdb2/src/index.ts index ea1dae62..aa5f2d40 100644 --- a/ranges/ssmdb2/src/index.ts +++ b/ranges/ssmdb2/src/index.ts @@ -1,12 +1,15 @@ import { createSSMDB2Client } from "dc-db-ssmdb2"; import "./cache/updater"; // Import the cache updater import { TableWatcherStream } from "./streams/tableWatcher"; -import { RangeDataStream } from "./streams/rangeData"; -import { DebounceStream } from "./streams/debounce"; -import { RabbitSenderStream } from "./streams/rabbitSender"; import { StabilizerStream } from "./streams/rangeStabilizer"; +import { RabbitMqWriter } from "dc-streams"; +import amqp from "amqplib"; +import { InternalRange } from "dc-ranges-types"; +import { DbQueryStream } from "./streams/dbQuery"; +import { RangeDataTranslator } from "./streams/rangeDataTranslator"; async function main() { + const connection = await amqp.connect("amqp://rabbitmq"); const ssmdb2Client = await createSSMDB2Client(); new TableWatcherStream( ssmdb2Client, @@ -15,10 +18,10 @@ async function main() { 100, 30000, ) - .pipe(new RangeDataStream(ssmdb2Client)) - .pipe(new StabilizerStream(20000)) - .pipe(new DebounceStream(500)) - .pipe(new RabbitSenderStream()); + .pipe(new DbQueryStream(ssmdb2Client)) + .pipe(new StabilizerStream(3000, 20000)) + .pipe(new RangeDataTranslator()) + .pipe(new RabbitMqWriter(connection, ["ranges.ssmdb2"], (value: InternalRange) => value.rangeId.toString())); } main(); diff --git a/ranges/ssmdb2/src/streams/rangeData.ts b/ranges/ssmdb2/src/streams/dbQuery.ts similarity index 83% rename from ranges/ssmdb2/src/streams/rangeData.ts rename to ranges/ssmdb2/src/streams/dbQuery.ts index eddf682f..b2f2f8c9 100644 --- a/ranges/ssmdb2/src/streams/rangeData.ts +++ b/ranges/ssmdb2/src/streams/dbQuery.ts @@ -1,23 +1,23 @@ -import { Transform, TransformCallback } from "stream"; +import { TransformCallback } from "stream"; import { Ssmdb2Client } from "dc-db-ssmdb2"; import { Hits, INVALID_HIT_POS, UnsignedInteger } from "dc-ranges-types"; -import { getDisciplineId } from "../cache/disciplines"; import { logger } from "dc-logger"; import { getLocalMs } from "../utils"; import { SSMDB2InternalRange } from "../types"; +import { TypedTransform } from "dc-streams"; -export class RangeDataStream extends Transform { +export class DbQueryStream extends TypedTransform { private readonly prisma: Ssmdb2Client; private readonly timeoutMs: number; constructor(prisma: Ssmdb2Client, timeoutMs: number = 20000) { - super({ objectMode: true }); + super(); this.prisma = prisma; this.timeoutMs = timeoutMs; } async _transform( - chunk: any, + chunk: string[], encoding: BufferEncoding, callback: TransformCallback, ): Promise { @@ -73,14 +73,9 @@ export class RangeDataStream extends Transform { targetId: targetId, rangeId: data.rangeId, startListId: data.startListId, - shooter: data.shooterId ? Number(data.shooterId) : null, + shooter: Number(data.shooterId) || null, hits: hits, - discipline: getDisciplineId( - data.disciplineId, - hits.length === 0 ? 0 : hits.length - 1, - ), - source: "ssmdb2", - ttl: data.timestamp.getTime() - getLocalMs() + this.timeoutMs, + disciplineId: data.disciplineId, }; } @@ -98,8 +93,8 @@ export class RangeDataStream extends Transform { const roundId = roundIdHasOffByOneBug ? hit.roundId - 1 : hit.roundId; - if (result[roundId] === undefined) { - result[roundId] = []; + while (result.length <= roundId) { + result.push([]); } if (hit.x >= INVALID_HIT_POS[0] && hit.y >= INVALID_HIT_POS[1]) { result[roundId]?.push({ @@ -120,6 +115,5 @@ export class RangeDataStream extends Transform { } return result .map((round) => round?.sort((a, b) => a.id - b.id)) - .map((round) => (round === undefined ? [] : round)); } } diff --git a/ranges/ssmdb2/src/streams/debounce.ts b/ranges/ssmdb2/src/streams/debounce.ts deleted file mode 100644 index 822c70fa..00000000 --- a/ranges/ssmdb2/src/streams/debounce.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { InternalRange } from "dc-ranges-types"; -import { Transform, TransformCallback } from "stream"; -import { logger } from "dc-logger"; - -type RangeDebounce = { - data: InternalRange; - debounce: NodeJS.Timeout; -} - -export class DebounceStream extends Transform { - private ranges: Map = new Map(); - private debounceTime: number; - - constructor(debounceTime: number) { - super({ objectMode: true }); - this.debounceTime = debounceTime; - } - - _transform(chunk: InternalRange, encoding: BufferEncoding, callback: TransformCallback): void { - logger.debug(`Received range ${chunk.rangeId} from RangeDataStream`); - if (this.ranges.has(chunk.rangeId)) { - const existingData = structuredClone(this.ranges.get(chunk.rangeId)!.data); - existingData.ttl = chunk.ttl; // Ignore TTL changes for debounce comparison - if (JSON.stringify(existingData) === JSON.stringify(chunk)) { - logger.debug(`Skipping range ${chunk.rangeId} due to no changes`); - callback(); - return; - } - clearTimeout(this.ranges.get(chunk.rangeId)!.debounce); - } - this.ranges.set(chunk.rangeId, { - data: chunk, - debounce: setTimeout(() => { - this.push(chunk); - this.ranges.delete(chunk.rangeId); - }, this.debounceTime) - }); - callback(); - } -} \ No newline at end of file diff --git a/ranges/ssmdb2/src/streams/rabbitSender.ts b/ranges/ssmdb2/src/streams/rabbitSender.ts deleted file mode 100644 index 57beb2af..00000000 --- a/ranges/ssmdb2/src/streams/rabbitSender.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Writable } from "stream"; -import amqp from "amqplib"; -import { InternalRange } from "dc-ranges-types"; -import { logger } from "dc-logger"; - -export class RabbitSenderStream extends Writable { - private channel: amqp.Channel | null = null; - private currentState: Map = new Map(); - - constructor() { - super({ objectMode: true }); - this.connect(); - } - - private async connect() { - const connection = await amqp.connect("amqp://rabbitmq"); - this.channel = await connection.createChannel(); - await this.channel.assertExchange("ranges.ssmdb2", "fanout", { - durable: false, - }); - } - - statesEqual(newState: InternalRange): boolean { - const oldState = this.currentState.get(newState.rangeId); - if (!oldState) { - return false; - } - const oldClone = structuredClone(oldState); - oldClone.ttl = newState.ttl; - return JSON.stringify(oldClone) == JSON.stringify(newState); - } - - _write( - chunk: InternalRange, - encoding: BufferEncoding, - callback: (error?: Error | null) => void, - ): void { - if (this.channel === null) { - logger.error("Channel not connected"); - callback(); - return; - } - if (this.statesEqual(chunk)) { - callback(); - return; - } - this.currentState.set(chunk.rangeId, chunk); - logger.info(`Sending range ${chunk.rangeId}`); - this.channel.publish( - "ranges.ssmdb2", - "", - Buffer.from(JSON.stringify(chunk)), - ); - setTimeout(() => { - this.currentState.delete(chunk.rangeId); - }, 5000); - callback(); - } -} diff --git a/ranges/ssmdb2/src/streams/rangeDataTranslator.ts b/ranges/ssmdb2/src/streams/rangeDataTranslator.ts new file mode 100644 index 00000000..d3bc5559 --- /dev/null +++ b/ranges/ssmdb2/src/streams/rangeDataTranslator.ts @@ -0,0 +1,26 @@ +import { TypedTransform } from "dc-streams"; +import { SSMDB2InternalRange } from "../types"; +import { InternalRange } from "dc-ranges-types"; +import { TransformCallback } from "node:stream"; +import { getDisciplineId } from "../cache/disciplines"; + +export class RangeDataTranslator extends TypedTransform { + + _transform( + chunk: SSMDB2InternalRange, + encoding: BufferEncoding, + callback: TransformCallback, + ): void { + const translated: InternalRange = { + rangeId: chunk.rangeId, + startListId: chunk.startListId, + shooter: chunk.shooter ? { type: "byId", id: chunk.shooter } : { type: "free" }, + hits: chunk.hits, + discipline: getDisciplineId(chunk.disciplineId, chunk.hits.length - 1), + source: "ssmdb2", + ttl: 25000, + } + this.push(translated); + callback(); + } +} diff --git a/ranges/ssmdb2/src/streams/rangeStabilizer.ts b/ranges/ssmdb2/src/streams/rangeStabilizer.ts index cc6334d1..2cfe7eaa 100644 --- a/ranges/ssmdb2/src/streams/rangeStabilizer.ts +++ b/ranges/ssmdb2/src/streams/rangeStabilizer.ts @@ -1,20 +1,56 @@ import { UnsignedInteger } from "dc-ranges-types"; -import { Transform, TransformCallback } from "stream"; +import { TransformCallback } from "stream"; import { logger } from "dc-logger"; import { SSMDB2InternalRange } from "../types"; +import { TypedTransform } from "dc-streams"; -type RangeStabilize = { +type StableCandidate = { data: SSMDB2InternalRange; + hash: string; timeout: NodeJS.Timeout; +} + +type RangeStabilize = { + stable: StableCandidate; + candidate?: StableCandidate; }; -export class StabilizerStream extends Transform { +export class StabilizerStream extends TypedTransform { private readonly ranges: Map = new Map(); private readonly resetTime: number; + private readonly candidateAcceptTime: number; - constructor(resetTime: number) { - super({ objectMode: true }); + constructor(candidateAcceptTime: number, resetTime: number) { + super(); this.resetTime = resetTime; + this.candidateAcceptTime = candidateAcceptTime; + } + + private hashRange(range: SSMDB2InternalRange): string { + return JSON.stringify(range); + } + + private isMoreHits(oldRange: SSMDB2InternalRange, newRange: SSMDB2InternalRange): boolean { + const newHits = newRange.hits; + const oldHits = oldRange.hits; + if (newHits.length > oldHits.length) { + return true; + } + if (newHits.length < oldHits.length) { + return false; + } + for (let i = 0; i < newHits.length; i++) { + if (oldHits[i] === undefined && newHits[i].length > 0) { + return true; + } + if (oldHits[i] === undefined && newHits[i] === undefined) { + continue; + } + if (newHits[i].length > oldHits[i].length) { + return true; + } + } + return false; } _transform( @@ -23,49 +59,72 @@ export class StabilizerStream extends Transform { callback: TransformCallback, ): void { logger.debug(`Received range ${chunk.rangeId} from RangeDataStream`); - if (!this.ranges.has(chunk.targetId)) { + const entry = this.ranges.get(chunk.targetId); + if (!entry) { this.ranges.set(chunk.targetId, { - data: chunk, - timeout: setTimeout(() => { - this.ranges.delete(chunk.targetId); - }, this.resetTime), + stable: { + data: chunk, + hash: this.hashRange(chunk), + timeout: setTimeout(() => { + this.ranges.delete(chunk.targetId); + }, this.resetTime), + } }); + this.push(structuredClone(chunk)); + callback(); + return; } - let changed = false; - const existingData = structuredClone( - this.ranges.get(chunk.targetId)!.data, - ); - for (let i = 0; i < chunk.hits.length; i++) { - const existingHits = existingData.hits[i]; - if (!existingHits) continue; - const newHitsLength = chunk.hits[i]?.length || 0; - if (existingHits.length > newHitsLength) { - chunk.hits[i] = existingHits; - changed = true; - logger.debug( - `Restored missing hits for range ${chunk.rangeId}, round ${i}`, - ); + + const stable = entry.stable; + + if (this.isMoreHits(stable.data, chunk)) { + logger.info(`Accepting new range ${chunk.rangeId} with more hits (${chunk.hits.flat().length} hits) than stable range (${stable.data.hits.flat().length} hits)`); + stable.data.hits = chunk.hits; + stable.hash = this.hashRange(stable.data); + stable.timeout.refresh(); + if (entry.candidate) { + clearTimeout(entry.candidate.timeout); + delete entry.candidate; } + this.push(structuredClone(stable.data)); + callback(); + return; } - if ( - chunk.discipline && - existingData.discipline && - chunk.discipline.roundId < existingData.discipline.roundId - ) { - chunk.discipline.roundId = existingData.discipline.roundId; - changed = true; - logger.debug(`Restored roundId for range ${chunk.rangeId}`); + + const chunkHash = this.hashRange(chunk); + + if (stable.hash === chunkHash) { + stable.timeout.refresh(); + if (entry.candidate) { + clearTimeout(entry.candidate.timeout); + delete entry.candidate; + } + this.push(structuredClone(stable.data)); + callback(); + return; } - if (!changed) { - clearTimeout(this.ranges.get(chunk.targetId)!.timeout); - this.ranges.set(chunk.targetId, { + + if (!entry.candidate) { + entry.candidate = { data: chunk, + hash: chunkHash, timeout: setTimeout(() => { - this.ranges.delete(chunk.targetId); - }, this.resetTime), - }); + if (entry.candidate) { + entry.stable.data = structuredClone(entry.candidate.data); + entry.stable.hash = entry.candidate.hash; + delete entry.candidate; + stable.timeout.refresh(); + this.push(structuredClone(entry.stable.data)); + } + }, this.candidateAcceptTime), + }; + } else { + entry.candidate.data = chunk; + entry.candidate.hash = chunkHash; + entry.candidate.timeout.refresh(); } - this.push(chunk); + this.push(structuredClone(stable.data)); callback(); + return; } -} +} \ No newline at end of file diff --git a/ranges/ssmdb2/src/streams/tableWatcher.ts b/ranges/ssmdb2/src/streams/tableWatcher.ts index 4b4fd682..41d49771 100644 --- a/ranges/ssmdb2/src/streams/tableWatcher.ts +++ b/ranges/ssmdb2/src/streams/tableWatcher.ts @@ -1,12 +1,12 @@ import { Ssmdb2Client } from "dc-db-ssmdb2"; +import { TypedReadable } from "dc-streams"; import { TableWatcherFast } from "dc-table-watcher"; -import { Readable } from "stream"; -export class TableWatcherStream extends Readable { +export class TableWatcherStream extends TypedReadable { private tableWatcher: TableWatcherFast; constructor(prisma: Ssmdb2Client, tables: string[], interval?: number, fastInterval?: number, fastTimeout?: number) { - super({ objectMode: true }); + super(); this.tableWatcher = new TableWatcherFast(prisma, tables, interval, fastInterval, fastTimeout); this.tableWatcher.on('change', (tables: string[]) => { this.push(tables); diff --git a/ranges/ssmdb2/src/types.ts b/ranges/ssmdb2/src/types.ts index 1a4b7ff4..b60c12a1 100644 --- a/ranges/ssmdb2/src/types.ts +++ b/ranges/ssmdb2/src/types.ts @@ -1,5 +1,10 @@ -import { InternalRange, UnsignedInteger } from "dc-ranges-types"; +import { Hits, Index, UnsignedInteger } from "dc-ranges-types"; -export type SSMDB2InternalRange = InternalRange & { +export type SSMDB2InternalRange = { + rangeId: Index; + startListId: Index; + disciplineId: Index; + hits: Array; + shooter: Index | null; targetId: UnsignedInteger; -}; +}; \ No newline at end of file diff --git a/ranges/ttl/package-lock.json b/ranges/ttl/package-lock.json index c01fcf38..368896a2 100644 --- a/ranges/ttl/package-lock.json +++ b/ranges/ttl/package-lock.json @@ -8,18 +8,18 @@ "name": "dc-ranges-ttl", "version": "1.0.0", "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/typescript": { @@ -37,9 +37,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" } diff --git a/ranges/ttl/package.json b/ranges/ttl/package.json index 67b25359..ce1a73ae 100644 --- a/ranges/ttl/package.json +++ b/ranges/ttl/package.json @@ -9,7 +9,7 @@ "build": "tsc" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" }, "files": [ diff --git a/ranges/types/package-lock.json b/ranges/types/package-lock.json index 0cfbf3f1..aa606c34 100644 --- a/ranges/types/package-lock.json +++ b/ranges/types/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -903,6 +903,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -912,9 +913,9 @@ } }, "node_modules/typia": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/typia/-/typia-11.0.0.tgz", - "integrity": "sha512-h1dQFo7m8XOzYkJ+YsxfAeWZeqroV81d+gXS168J4jFWjGp+47k/NizSXr/G/CXBCLlWSK86qfzoWTCPJeonIg==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/typia/-/typia-11.0.3.tgz", + "integrity": "sha512-L7x7WzOCpFyNCauWl6VYJVEG9EHZi5EPNBRzxTO1luaLCd6WEDf+xrJNT+hMZ8U+0X7hCsR1EUpi29LdHhvCvA==", "license": "MIT", "dependencies": { "@samchon/openapi": "^6.0.0", diff --git a/ranges/types/package.json b/ranges/types/package.json index 79699248..54aedb3c 100644 --- a/ranges/types/package.json +++ b/ranges/types/package.json @@ -13,7 +13,7 @@ "license": "ISC", "description": "", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", diff --git a/ranges/types/src/common/index.ts b/ranges/types/src/common/index.ts index 21d1dde5..46fe1e86 100644 --- a/ranges/types/src/common/index.ts +++ b/ranges/types/src/common/index.ts @@ -1,14 +1,10 @@ -import { Maximum } from "typia/lib/tags/Maximum.js"; -import { Minimum } from "typia/lib/tags/Minimum.js"; -import { MultipleOf } from "typia/lib/tags/MultipleOf.js"; -import { MinLength } from "typia/lib/tags/MinLength.js"; -import { MaxLength } from "typia/lib/tags/MaxLength.js"; +import { tags } from "typia"; -export type Integer = number & MultipleOf<1>; -export type UnsignedNumber = number & Minimum<0>; +export type Integer = number & tags.MultipleOf<1>; +export type UnsignedNumber = number & tags.Minimum<0>; export type UnsignedInteger = Integer & UnsignedNumber; -export type RangeId = Integer & Minimum<1> & Maximum<416>; +export type RangeId = Integer & tags.Minimum<1> & tags.Maximum<416>; export type Index = UnsignedInteger; -export type ColorCode = string & MinLength<7> & MaxLength<7>; \ No newline at end of file +export type ColorCode = string & tags.MinLength<7> & tags.MaxLength<7>; \ No newline at end of file diff --git a/ranges/types/src/hits/index.ts b/ranges/types/src/hits/index.ts index 83d659cc..f66dd28d 100644 --- a/ranges/types/src/hits/index.ts +++ b/ranges/types/src/hits/index.ts @@ -1,11 +1,14 @@ -import { Index, UnsignedInteger, UnsignedNumber } from "../common/index.js"; +import { tags } from "typia"; +import { Index, UnsignedNumber } from "../common/index.js"; + +export type HitIndex = Index & tags.Maximum<1000>; export type Hits = Array; export type Hit = InvalidHit | ValidHit; export type BaseHit = { - id: Index; + id: HitIndex; valid: boolean; } diff --git a/ranges/types/src/index.ts b/ranges/types/src/index.ts index 1b961502..e7e6fc5a 100644 --- a/ranges/types/src/index.ts +++ b/ranges/types/src/index.ts @@ -6,7 +6,7 @@ import { Layout, LayoutChess, LayoutDart, LayoutEaster, LayoutRectangle, LayoutR import { Round, Rounds } from './discipline/round/index.js'; import { Mode } from './discipline/round/mode.js'; import { Zoom } from './discipline/round/zoom.js'; -import { InternalShooter, InternalShooterByName, Shooter, ShooterId } from './shooter/index.js'; +import { InternalShooter, InternalShooterByName, RangeShooter, Shooter, ShooterId } from './shooter/index.js'; import { StartList } from './startList/index.js'; import { UnsignedInteger, ColorCode, Index, Integer, RangeId, UnsignedNumber } from './common/index.js'; export type Range = InactiveRange | ActiveRange; @@ -24,7 +24,7 @@ export type ActiveRange = BaseRange & { // targetId: number; round: number; - shooter: Shooter | null; + shooter: RangeShooter | null; startList: StartList | null; discipline: Discipline | null; hits: Array; @@ -88,7 +88,8 @@ export { UnsignedNumber, OverrideDiscipline, Shooter, - ShooterId + ShooterId, + RangeShooter } // The ShootMaster software represents a invalid hit as the maximum 32-bit integer value. diff --git a/ranges/types/src/shooter/index.ts b/ranges/types/src/shooter/index.ts index 8ca1f28e..10e75a21 100644 --- a/ranges/types/src/shooter/index.ts +++ b/ranges/types/src/shooter/index.ts @@ -1,17 +1,37 @@ -import { createIs } from "typia"; -import { Index, UnsignedInteger } from "../common/index.js"; +import { Index } from "../common/index.js"; export type ShooterId = Index; -export type Shooter = { - id: ShooterId | null; // Null = Can't be determined uniquely +type ShooterName = { firstName: string; lastName: string; } -export type InternalShooter = ShooterId | InternalShooterByName; +export type Shooter = { + id: ShooterId | null; // Null = Can't be determined uniquely +} & ShooterName; -export type InternalShooterByName = { - firstName: string, - lastName: string, +export type ShooterFree = { + type: "free"; +} + +export type ShooterOccupied = { + type: "occupied"; +} & Shooter; + +export type RangeShooter = ShooterFree | ShooterOccupied; + +export type InternalShooter = InternalShooterFree | InternalShooterById | InternalShooterByName; + +export type InternalShooterFree = { + type: "free"; } + +export type InternalShooterById = { + type: "byId"; + id: ShooterId; +} + +export type InternalShooterByName = { + type: "byName"; +} & ShooterName; diff --git a/screens/evaluations/package-lock.json b/screens/evaluations/package-lock.json index 5483079e..6b045c8f 100644 --- a/screens/evaluations/package-lock.json +++ b/screens/evaluations/package-lock.json @@ -11,16 +11,16 @@ "dependencies": { "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "jsdom": "^27.4.0", + "express-rate-limit": "^8.3.1", + "jsdom": "^28.1.0", "socket.io-client": "^4.8.3" }, "devDependencies": { "@types/express": "^5.0.6", - "@types/jsdom": "^27.0.0", - "@types/node": "^25.0.9", + "@types/jsdom": "^28.0.0", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -30,15 +30,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -47,45 +47,45 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, "node_modules/@acemir/cssom": { - "version": "0.9.29", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.29.tgz", - "integrity": "sha512-G90x0VW+9nW4dFajtjCoT+NM0scAfH9Mb08IcjgFHYbfiL/lU04dTF9JuVOi3/OH+DJCQdcIseSXkdCB9Ky6JA==", + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", - "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.4" + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", - "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", "license": "MIT", "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.6" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -94,10 +94,22 @@ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "license": "MIT" }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", + "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", "funding": [ { "type": "github", @@ -110,13 +122,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "funding": [ { "type": "github", @@ -129,17 +141,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", + "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", "funding": [ { "type": "github", @@ -152,21 +164,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.1", + "@csstools/css-calc": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "funding": [ { "type": "github", @@ -178,17 +190,18 @@ } ], "license": "MIT", + "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.21.tgz", - "integrity": "sha512-plP8N8zKfEZ26figX4Nvajx8DuzfuRpLTqglQ5d0chfnt35Qt3X+m6ASZ+rG0D0kxe/upDVNwSIVJP5n4FuNfw==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", + "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", "funding": [ { "type": "github", @@ -199,15 +212,12 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } + "license": "MIT-0" }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "funding": [ { "type": "github", @@ -219,23 +229,24 @@ } ], "license": "MIT", + "peer": true, "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@exodus/bytes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.8.0.tgz", - "integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@exodus/crypto": "^1.0.0-rc.4" + "@noble/hashes": "^1.8.0 || ^2.0.0" }, "peerDependenciesMeta": { - "@exodus/crypto": { + "@noble/hashes": { "optional": true } } @@ -338,15 +349,16 @@ "license": "MIT" }, "node_modules/@types/jsdom": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", - "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-A8TBQQC/xAOojy9kM8E46cqT00sF0h7dWjV8t8BJhUi2rG6JRh7XXQo/oLoENuZIQEpXsxLccLCnknyQd7qssQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", - "parse5": "^7.0.0" + "parse5": "^7.0.0", + "undici-types": "^7.21.0" } }, "node_modules/@types/mime": { @@ -357,15 +369,22 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -656,30 +675,31 @@ } }, "node_modules/cssstyle": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", - "integrity": "sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.1.tgz", + "integrity": "sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.1.1", - "@csstools/css-syntax-patches-for-csstree": "^1.0.21", - "css-tree": "^3.1.0" + "@asamuzakjp/css-color": "^4.1.2", + "@csstools/css-syntax-patches-for-csstree": "^1.0.26", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.5" }, "engines": { "node": ">=20" } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/dc-db-local": { @@ -736,9 +756,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -898,6 +918,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -937,12 +958,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -1248,9 +1269,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -1324,16 +1345,17 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", - "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "license": "MIT", "dependencies": { - "@acemir/cssom": "^0.9.28", - "@asamuzakjp/dom-selector": "^6.7.6", - "@exodus/bytes": "^1.6.0", - "cssstyle": "^5.3.4", - "data-urls": "^6.0.0", + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", @@ -1343,11 +1365,11 @@ "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -1375,9 +1397,9 @@ } }, "node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -2140,10 +2162,19 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.22.0.tgz", + "integrity": "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw==", "dev": true, "license": "MIT" }, @@ -2178,34 +2209,35 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "license": "BSD-2-Clause", "engines": { "node": ">=20" } }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", + "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", "license": "MIT", "dependencies": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/wrappy": { @@ -2214,27 +2246,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "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 - } - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/screens/evaluations/package.json b/screens/evaluations/package.json index d9a0b1e4..debb5902 100644 --- a/screens/evaluations/package.json +++ b/screens/evaluations/package.json @@ -12,16 +12,16 @@ "dependencies": { "dc-db-local": "file:../../database/local", "dc-logger": "file:../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "jsdom": "^27.4.0", + "express-rate-limit": "^8.3.1", + "jsdom": "^28.1.0", "socket.io-client": "^4.8.3" }, "devDependencies": { "@types/express": "^5.0.6", - "@types/jsdom": "^27.0.0", - "@types/node": "^25.0.9", + "@types/jsdom": "^28.0.0", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } diff --git a/screens/images/package-lock.json b/screens/images/package-lock.json index 8012cdd7..792c66fa 100644 --- a/screens/images/package-lock.json +++ b/screens/images/package-lock.json @@ -11,16 +11,16 @@ "dependencies": { "body-parser": "^2.2.2", "dc-logger": "file:../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "express-fileupload": "^1.5.2", - "express-rate-limit": "^8.2.1", + "express-rate-limit": "^8.3.1", "pdf2pic": "^3.2.0" }, "devDependencies": { "@types/express": "^5.0.6", "@types/express-fileupload": "^1.5.1", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -30,12 +30,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -159,13 +159,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -484,9 +484,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -625,12 +625,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -939,9 +939,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -1659,9 +1659,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/screens/images/package.json b/screens/images/package.json index 131a59f7..dbbf56cc 100644 --- a/screens/images/package.json +++ b/screens/images/package.json @@ -12,16 +12,16 @@ "dependencies": { "body-parser": "^2.2.2", "dc-logger": "file:../../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "express-fileupload": "^1.5.2", - "express-rate-limit": "^8.2.1", + "express-rate-limit": "^8.3.1", "pdf2pic": "^3.2.0" }, "devDependencies": { "@types/express": "^5.0.6", "@types/express-fileupload": "^1.5.1", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } diff --git a/screens/images/src/index.ts b/screens/images/src/index.ts index 07af5b92..1cb31843 100644 --- a/screens/images/src/index.ts +++ b/screens/images/src/index.ts @@ -1,5 +1,6 @@ import express, { Express, Request } from "express"; import * as fs from "fs"; +import * as nodePath from "path"; import fileUpload from "express-fileupload"; import { fromPath } from "pdf2pic"; import { scanDirectory } from "@shared/files/scanDir"; @@ -7,16 +8,35 @@ import { logger } from "dc-logger"; import bodyParser from "body-parser"; import { rateLimit } from "express-rate-limit"; -const basePath = `/app/files`; +const basePath = nodePath.resolve(`/app/files`); + +/** + * Resolves a user-supplied path relative to basePath and verifies it stays + * within basePath. Returns null if the resolved path would escape basePath. + */ +function resolveSafePath(userInput: string): string | null { + const resolved = nodePath.resolve(basePath, userInput); + if (resolved !== basePath && !resolved.startsWith(basePath + nodePath.sep)) { + return null; + } + return resolved; +} + +/** + * Sanitizes a filename by removing path separators, null bytes, and other + * characters that could be used for path traversal or injection attacks. + */ +function sanitizeFileName(name: string): string { + return nodePath.basename(name).replace(/[\0]/g, '_'); +} if (!fs.existsSync(basePath)) { logger.debug("Creating base path"); fs.mkdirSync(basePath, { recursive: true }); } -if (!fs.existsSync(`${basePath}/icon.png`)) { +if (!fs.existsSync(nodePath.join(basePath, "icon.png"))) { logger.debug("Creating icon.png"); - - fs.copyFileSync(`${__dirname}/img/icon.png`, `${basePath}/icon.png`); + fs.copyFileSync(nodePath.join(__dirname, "img", "icon.png"), nodePath.join(basePath, "icon.png")); } const app: Express = express(); @@ -39,12 +59,12 @@ app.set("trust proxy", 1); app.get("/api/images{/*files}", async (req: Request, res) => { const path = (req.params.files as unknown as string[] || []).join("/"); logger.info(`GET ${path}`); - if (path.includes("..")) { - logger.info("Found .. in path"); + const realPath = resolveSafePath(path); + if (!realPath) { + logger.warn(`SECURITY: Path traversal attempt detected. Path: ${path}, IP: ${req.ip}`); res.status(404).sendFile("img/404.png", { root: __dirname }); return; } - const realPath = `${basePath}/${path}`; if (!fs.existsSync(realPath)) { logger.info("File not found"); res.status(404).sendFile("img/404.png", { root: __dirname }); @@ -75,6 +95,7 @@ async function handleFiles(files: fileUpload.FileArray, path: string) { } async function handleFile(file: fileUpload.UploadedFile, path: string) { + const safeFileName = sanitizeFileName(file.name); if (file.mimetype === "application/pdf") { logger.info("Converting PDF to images"); let minDimension = 1920; @@ -83,13 +104,13 @@ async function handleFile(file: fileUpload.UploadedFile, path: string) { minDimension = Math.min(...screenResolution.filter((v) => !isNaN(v)), minDimension); } logger.debug(`Using resolution ${minDimension}`); - if (fs.existsSync(`${path}/${file.name}`)) { - fs.rmSync(`${path}/${file.name}`, { recursive: true }); + if (fs.existsSync(`${path}/${safeFileName}`)) { + fs.rmSync(`${path}/${safeFileName}`, { recursive: true }); } - fs.mkdirSync(`${path}/${file.name}`); + fs.mkdirSync(`${path}/${safeFileName}`); const pdf = fromPath(file.tempFilePath, { density: 300, - savePath: path + "/" + file.name, + savePath: path + "/" + safeFileName, saveFilename: "page", width: minDimension, height: minDimension, @@ -105,26 +126,26 @@ async function handleFile(file: fileUpload.UploadedFile, path: string) { continue; } const fileNumber = img.page.toString().padStart(digitNumber, "0"); - const path = img.path.split("/").slice(0, -1).join("/"); - await fs.promises.rename(img.path, `${path}/page-${fileNumber}.png`); + const imgDir = img.path.split("/").slice(0, -1).join("/"); + await fs.promises.rename(img.path, `${imgDir}/page-${fileNumber}.png`); } } else { - await file.mv(`${path}/${file.name}`); + await file.mv(`${path}/${safeFileName}`); } } app.post("/api/images{/*files}", async (req: Request, res) => { const path = (req.params.files as unknown as string[] || []).join("/"); logger.info(`POST ${path}`); - if (path.includes("..")) { - logger.info("Found .. in path"); + const realPath = resolveSafePath(path); + if (!realPath) { + logger.warn(`SECURITY: Path traversal attempt detected. Path: ${path}, IP: ${req.ip}`); res.status(400).send({ code: 400, message: "Invalid path", }) return; } - const realPath = `${basePath}/${path}`; let created = false; if (!fs.existsSync(realPath)) { logger.debug("Creating folder"); @@ -163,14 +184,15 @@ app.post("/api/images{/*files}", async (req: Request, res) => { app.delete("/api/images{/*files}", async (req: Request, res) => { const path = (req.params.files as unknown as string[] || []).join("/"); logger.info(`DELETE ${path}`); - if (path.includes("..")) { + const realPath = resolveSafePath(path); + if (!realPath) { + logger.warn(`SECURITY: Path traversal attempt detected. Path: ${path}, IP: ${req.ip}`); res.status(400).send({ code: 400, message: "Invalid path", }) return; } - const realPath = `${basePath}/${path}`; if (!fs.existsSync(realPath)) { res.status(404).send({ code: 404, @@ -197,14 +219,15 @@ app.delete("/api/images{/*files}", async (req: Request, res) => { app.put("/api/images{/*files}", async (req: Request, res) => { const path = (req.params.files as unknown as string[] || []).join("/"); logger.info(`PUT ${path}`); - if (path.includes("..")) { + const realPath = resolveSafePath(path); + if (!realPath) { + logger.warn(`SECURITY: Path traversal attempt detected. Path: ${path}, IP: ${req.ip}`); res.status(400).send({ code: 400, message: "Invalid path", }) return; } - const realPath = `${basePath}/${path}`; if (!fs.existsSync(realPath)) { res.status(404).send({ code: 404, @@ -235,14 +258,15 @@ app.put("/api/images{/*files}", async (req: Request, res) => { }); return; } - if (destination.includes("..")) { + const realDestination = resolveSafePath(destination); + if (!realDestination) { + logger.warn(`SECURITY: Path traversal attempt detected in destination. Destination: ${destination}, IP: ${req.ip}`); res.status(400).send({ code: 400, message: "Invalid new path", }) return; } - const realDestination = `${basePath}/${destination}`; if (realPath === realDestination) { res.status(400).send({ code: 400, diff --git a/screens/screenCast/package-lock.json b/screens/screenCast/package-lock.json index ce9c5023..22b5b4ff 100644 --- a/screens/screenCast/package-lock.json +++ b/screens/screenCast/package-lock.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -25,12 +25,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -39,7 +39,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -72,12 +72,12 @@ } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/accepts": { @@ -327,9 +327,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/url-parse": { diff --git a/screens/screenCast/package.json b/screens/screenCast/package.json index d4da9373..79469ce7 100644 --- a/screens/screenCast/package.json +++ b/screens/screenCast/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@types/amqplib": "^0.10.8", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } } diff --git a/screens/screenManager/package-lock.json b/screens/screenManager/package-lock.json index 7d54af0b..8a45990c 100644 --- a/screens/screenManager/package-lock.json +++ b/screens/screenManager/package-lock.json @@ -15,14 +15,14 @@ "dc-logger": "file:../../logger", "dc-ranges-types": "file:../../ranges/types", "dc-screens-types": "file:../types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "socket.io": "^4.8.3" }, "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } @@ -32,15 +32,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -49,12 +49,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -63,7 +63,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -75,7 +75,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -206,12 +206,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -546,9 +546,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1825,9 +1825,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unpipe": { diff --git a/screens/screenManager/package.json b/screens/screenManager/package.json index cd0fe538..2b049beb 100644 --- a/screens/screenManager/package.json +++ b/screens/screenManager/package.json @@ -16,14 +16,14 @@ "dc-logger": "file:../../logger", "dc-ranges-types": "file:../../ranges/types", "dc-screens-types": "file:../types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", "socket.io": "^4.8.3" }, "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "tsc-alias": "^1.8.16", "typescript": "^5.9.3" } diff --git a/screens/screenManager/src/server/index.ts b/screens/screenManager/src/server/index.ts index d6b92fdb..f41aa0bd 100644 --- a/screens/screenManager/src/server/index.ts +++ b/screens/screenManager/src/server/index.ts @@ -60,7 +60,6 @@ app.post('/api/screens/next', (req: Request, res: Response) => { } catch (error) { res.status(500).send('Internal server error'); } - res.end(); }); app.post('/api/screens/previous', (req: Request, res: Response) => { @@ -70,7 +69,6 @@ app.post('/api/screens/previous', (req: Request, res: Response) => { } catch (error) { res.status(500).send('Internal server error'); } - res.end(); }); app.post('/api/screens/resolve', async (req: Request, res: Response) => { @@ -87,7 +85,8 @@ app.post('/api/screens/resolve', async (req: Request, res: Response) => { const resolvedScreens = await resolveScreen(screen); res.status(200).send(resolvedScreens); } catch (error) { - res.status(500).send(`Internal server error: ${error}`); + logger.error(error); + res.status(500).send('Internal server error'); } }); @@ -163,6 +162,10 @@ app.put('/api/screens/:screenId', async (req: Request, res: Response) => { return; } const screenId = Number(req.params.screenId); + if (!req.body || !isDbScreen(req.body)) { + res.status(400).send('Invalid screen data'); + return; + } try { await localClient.screens.upsert({ where: { diff --git a/screens/types/package-lock.json b/screens/types/package-lock.json index 95127afb..74c1ed9b 100644 --- a/screens/types/package-lock.json +++ b/screens/types/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -889,6 +889,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -898,9 +899,9 @@ } }, "node_modules/typia": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/typia/-/typia-11.0.0.tgz", - "integrity": "sha512-h1dQFo7m8XOzYkJ+YsxfAeWZeqroV81d+gXS168J4jFWjGp+47k/NizSXr/G/CXBCLlWSK86qfzoWTCPJeonIg==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/typia/-/typia-11.0.3.tgz", + "integrity": "sha512-L7x7WzOCpFyNCauWl6VYJVEG9EHZi5EPNBRzxTO1luaLCd6WEDf+xrJNT+hMZ8U+0X7hCsR1EUpi29LdHhvCvA==", "license": "MIT", "dependencies": { "@samchon/openapi": "^6.0.0", diff --git a/screens/types/package.json b/screens/types/package.json index b3a2f114..ba76705e 100644 --- a/screens/types/package.json +++ b/screens/types/package.json @@ -20,6 +20,6 @@ "typescript": "~5.9.3" }, "dependencies": { - "typia": "^11.0.0" + "typia": "^11.0.3" } } \ No newline at end of file diff --git a/serverState/package-lock.json b/serverState/package-lock.json index 1bfb8763..e37aff64 100644 --- a/serverState/package-lock.json +++ b/serverState/package-lock.json @@ -14,15 +14,15 @@ "dc-db-smdb": "file:../database/smdb", "dc-logger": "file:../logger", "dc-screens-types": "file:../screens/types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "socket.io": "^4.8.3" }, "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "@types/semver": "^7.7.1", "typescript": "^5.9.3" } @@ -32,15 +32,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -48,14 +48,14 @@ "name": "dc-db-smdb", "version": "1.0.0", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -64,12 +64,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -78,7 +78,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "typia": "^11.0.0" + "typia": "^12.0.0" }, "devDependencies": { "ts-patch": "^3.3.0", @@ -171,12 +171,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -420,9 +420,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -951,9 +951,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1044,9 +1044,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1335,9 +1335,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unpipe": { diff --git a/serverState/package.json b/serverState/package.json index 09ccccde..52e9af2b 100644 --- a/serverState/package.json +++ b/serverState/package.json @@ -15,15 +15,15 @@ "dc-db-smdb": "file:../database/smdb", "dc-logger": "file:../logger", "dc-screens-types": "file:../screens/types", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "express": "^5.2.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "socket.io": "^4.8.3" }, "devDependencies": { "@types/amqplib": "^0.10.8", "@types/express": "^5.0.6", - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "@types/semver": "^7.7.1", "typescript": "^5.9.3" } diff --git a/serverState/src/checkServer.ts b/serverState/src/checkServer.ts index 1d33fab8..9c60b5ce 100644 --- a/serverState/src/checkServer.ts +++ b/serverState/src/checkServer.ts @@ -89,7 +89,6 @@ function checkServerCompatibility(serverState: AdvServerState): boolean { return false; } if (!serverState.services.smdb) { - logger.warn("SMDB service is not available"); return false; } if (!process.env.MAX_SM_VERSION || !process.env.MIN_SM_VERSION) { @@ -119,12 +118,12 @@ function checkServerCompatibility(serverState: AdvServerState): boolean { async function checkDatabaseAvailability(): Promise { const client = await createSMDBClient(prismaClient); try { - await client.$connect(); - await client.$disconnect(); + await client.version.findFirst(); return true; } catch (e) { - logger.error("Error while checking database availability: " + e); return false; + } finally { + await client.$disconnect().catch(); } } \ No newline at end of file diff --git a/streams/package-lock.json b/streams/package-lock.json new file mode 100644 index 00000000..8c4671de --- /dev/null +++ b/streams/package-lock.json @@ -0,0 +1,122 @@ +{ + "name": "dc-streams", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dc-streams", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "amqplib": "^0.10.9", + "dc-logger": "file:../logger" + }, + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/node": "^25.5.0", + "typescript": "~5.9.3" + } + }, + "../logger": { + "name": "dc-logger", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.3.1", + "pino": "^10.3.1", + "pino-pretty": "^13.1.3" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@types/amqplib": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz", + "integrity": "sha512-vtDp8Pk1wsE/AuQ8/Rgtm6KUZYqcnTgNvEHwzCkX8rL7AGsC6zqAfKAAJhUZXFhM/Pp++tbnUHiam/8vVpPztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/amqplib": { + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", + "integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==", + "license": "MIT", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", + "license": "MIT" + }, + "node_modules/dc-logger": { + "resolved": "../logger", + "link": true + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + } + } +} diff --git a/streams/package.json b/streams/package.json new file mode 100644 index 00000000..f1264698 --- /dev/null +++ b/streams/package.json @@ -0,0 +1,25 @@ +{ + "name": "dc-streams", + "version": "1.0.0", + "description": "Streams helpers for displaycontroller", + "license": "ISC", + "author": "Sven Finn", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/node": "^25.5.0", + "typescript": "~5.9.3" + }, + "dependencies": { + "amqplib": "^0.10.9", + "dc-logger": "file:../logger" + } +} \ No newline at end of file diff --git a/streams/src/core/duplex.ts b/streams/src/core/duplex.ts new file mode 100644 index 00000000..127e0e6b --- /dev/null +++ b/streams/src/core/duplex.ts @@ -0,0 +1,29 @@ +import { Duplex } from "node:stream"; + +export class TypedDuplex extends Duplex { + declare _inputType: I; + declare _outputType: O; + + constructor() { + super({ + readableObjectMode: true, + writableObjectMode: true + }); + } + + // Typed writable side + write(chunk: I): boolean { + return super.write(chunk); + } + + // Typed readable side + push(chunk: O | null): boolean { + return super.push(chunk); + } + + // Typed data listener + override on(event: "data", listener: (chunk: O) => void): this; + override on(event: string, listener: (...args: any[]) => void): this { + return super.on(event, listener); + } +} diff --git a/streams/src/core/readable.ts b/streams/src/core/readable.ts new file mode 100644 index 00000000..9745245b --- /dev/null +++ b/streams/src/core/readable.ts @@ -0,0 +1,22 @@ +import { Readable, Writable } from "stream"; +import { TypedWritable } from "./writable.js"; +import { TypedTransform } from "./transform.js"; + +export class TypedReadable extends Readable { + declare _outputType: O; + + constructor() { + super({ objectMode: true }) + } + + push(chunk: O | null): boolean { + return super.push(chunk); + } + + pipe(dest: TypedTransform): TypedTransform; + pipe(dest: TypedWritable): TypedWritable; + pipe(dest: Writable): Writable; + pipe(dest: Writable): Writable { + return super.pipe(dest); + } +} \ No newline at end of file diff --git a/streams/src/core/transform.ts b/streams/src/core/transform.ts new file mode 100644 index 00000000..ff56b60c --- /dev/null +++ b/streams/src/core/transform.ts @@ -0,0 +1,28 @@ +import { Transform, TransformCallback, Writable } from "stream"; +import { TypedWritable } from "./writable.js"; + +export abstract class TypedTransform extends Transform { + declare _inputType: I; + declare _outputType: O; + + constructor() { + super({ objectMode: true }) + } + + abstract _transform( + chunk: I, + encoding: BufferEncoding, + callback: TransformCallback + ): void; + + push(chunk: O | null): boolean { + return super.push(chunk); + } + + pipe(dest: TypedTransform): TypedTransform; + pipe(dest: TypedWritable): TypedWritable; + pipe(dest: Writable): Writable; + pipe(dest: Writable): Writable { + return super.pipe(dest); + } +} \ No newline at end of file diff --git a/streams/src/core/writable.ts b/streams/src/core/writable.ts new file mode 100644 index 00000000..d5c7aa79 --- /dev/null +++ b/streams/src/core/writable.ts @@ -0,0 +1,13 @@ +import { Writable } from "stream"; + +export class TypedWritable extends Writable { + declare _inputType: I; + + constructor() { + super({ objectMode: true }) + } + + write(chunk: I): boolean { + return super.write(chunk) + } +} \ No newline at end of file diff --git a/streams/src/index.ts b/streams/src/index.ts new file mode 100644 index 00000000..85832c2d --- /dev/null +++ b/streams/src/index.ts @@ -0,0 +1,10 @@ + +export type IdFn = (value: T) => string; + +export { TypedReadable } from "./core/readable.js"; +export { TypedTransform } from "./core/transform.js"; +export { TypedWritable } from "./core/writable.js"; +export { TypedDuplex } from "./core/duplex.js"; +export { RabbitMqWriter } from "./rabbitMq/writer.js"; +export { RabbitMqReceiver } from "./rabbitMq/receiver.js"; +export { DebounceTransform } from "./transforms/debounce.js"; \ No newline at end of file diff --git a/streams/src/rabbitMq/receiver.ts b/streams/src/rabbitMq/receiver.ts new file mode 100644 index 00000000..50c6e29d --- /dev/null +++ b/streams/src/rabbitMq/receiver.ts @@ -0,0 +1,71 @@ +import { ChannelModel, Channel, Message } from "amqplib"; +import { logger } from "dc-logger"; +import { TypedReadable } from "../core/readable.js"; + +type IsFn = (input: unknown) => input is T; + +export class RabbitMqReceiver extends TypedReadable { + private paused = false; + private pendingMsg: Message | null = null; + private channel: Channel | null = null; + private readonly isFn: IsFn; + + constructor(connection: ChannelModel, exchanges: Array, isFn: IsFn) { + super(); + this.isFn = isFn; + this.connect(connection, exchanges); + } + + private async connect(connection: ChannelModel, exchanges: Array) { + const channel = await connection.createChannel(); + this.channel = channel; + + await channel.prefetch(1); + await Promise.all(exchanges.map(async (exchange) => { + await channel.assertExchange(exchange, "fanout", { durable: false }); + })); + + const queue = await channel.assertQueue("", { exclusive: true }); + await Promise.all(exchanges.map(async (exchange) => { + await channel.bindQueue(queue.queue, exchange, ""); + })); + + channel.consume(queue.queue, (msg) => { + if (!msg || this.paused) return; + + const message = JSON.parse(msg.content.toString()); + if (!this.isFn(message)) { + logger.error("Received invalid message"); + channel.ack(msg); + return; + } + + const ok = this.push(message); + if (ok) { + channel.ack(msg); + } else { + this.paused = true; + this.pendingMsg = msg; + } + }); + } + + _read() { + if (!this.paused || !this.pendingMsg || !this.channel) { + return; + } + + const msg = this.pendingMsg; + this.pendingMsg = null; + + const message = JSON.parse(msg.content.toString()); + + const ok = this.push(message); + if (ok) { + this.channel.ack(msg); + this.paused = false; + } else { + this.pendingMsg = msg; + } + } +} diff --git a/streams/src/rabbitMq/writer.ts b/streams/src/rabbitMq/writer.ts new file mode 100644 index 00000000..55fdacda --- /dev/null +++ b/streams/src/rabbitMq/writer.ts @@ -0,0 +1,92 @@ +import { Channel, ChannelModel } from "amqplib"; +import { logger } from "dc-logger"; +import { TypedWritable } from "../core/writable.js"; +import { IdFn } from "../index.js"; + +type State = { + state: string, + timeout: NodeJS.Timeout +}; + +export class RabbitMqWriter extends TypedWritable { + private channel: Channel | null = null; + private readonly exchanges: readonly string[]; + private readonly currentState: Map = new Map(); + private readonly idFn: IdFn; + private readonly resendTimeout: number; + private readonly ready: Promise; + + constructor(connection: ChannelModel, exchanges: string[], idFn: IdFn, resendTimeout: number = 5000) { + super(); + this.idFn = idFn; + this.resendTimeout = resendTimeout; + this.exchanges = exchanges; + + this.ready = this.connect(connection, exchanges); + } + + private async connect(connection: ChannelModel, exchanges: string[]) { + const channel = await connection.createChannel(); + this.channel = channel; + + await Promise.all( + exchanges.map(exchange => + channel.assertExchange(exchange, "fanout", { durable: false }) + ) + ); + } + + _write(chunk: T, encoding: BufferEncoding, callback: (error?: Error | null) => void): void { + this.ready + .then(() => this._writeInternal(chunk, callback)) + .catch(err => callback(err)); + } + + private _writeInternal(chunk: T, callback: (error?: Error | null) => void) { + try { + if (this.channel === null) { + return callback(new Error("RabbitMq channel not connected")); + } + + const id = this.idFn(chunk); + const json = JSON.stringify(chunk); + + const lastState = this.currentState.get(id); + + if (lastState && lastState.state === json) { + logger.debug(`${id} already sent`); + return callback(); + } + + if (lastState) { + clearTimeout(lastState.timeout); + } + + logger.debug(`Sending ${id}`); + for (const exchange of this.exchanges) { + this.channel.publish(exchange, "", Buffer.from(json)); + } + + const state: State = { + state: json, + timeout: setTimeout(() => { + this.currentState.delete(id); + }, this.resendTimeout) + }; + + this.currentState.set(id, state); + + callback(); + } catch (err) { + callback(err as Error); + } + } + + _destroy(error: Error | null, callback: (error?: Error | null) => void): void { + for (const state of this.currentState.values()) { + clearTimeout(state.timeout); + } + this.currentState.clear(); + callback(error); + } +} diff --git a/streams/src/transforms/debounce.ts b/streams/src/transforms/debounce.ts new file mode 100644 index 00000000..306431a5 --- /dev/null +++ b/streams/src/transforms/debounce.ts @@ -0,0 +1,72 @@ +import { TransformCallback } from "node:stream"; +import { logger } from "dc-logger"; +import { TypedTransform } from "../core/transform.js"; +import { IdFn } from "../index.js"; + +type DebounceData = { + data: T + json: string, + timeout: NodeJS.Timeout +} + +export class DebounceTransform extends TypedTransform { + private readonly data: Map> = new Map(); + private readonly debounceTime: number; + private readonly idFn: IdFn; + + constructor(idFn: IdFn, debounceTime: number = 300) { + super(); + this.idFn = idFn; + this.debounceTime = debounceTime; + } + + _transform(chunk: T, encoding: BufferEncoding, callback: TransformCallback): void { + const id = this.idFn(chunk); + const json = JSON.stringify(chunk); + + const entry = this.data.get(id); + if (entry && entry.json === json) { + logger.debug(`Skipping chunk ${id} due to no changes`); + return callback(); + } + if (entry) { + clearTimeout(entry.timeout); + } + const newEntry = { + data: chunk, + json: json, + timeout: setTimeout(() => { + const current = this.data.get(id); + if (current === newEntry) { + this.data.delete(id); + if (!this.destroyed) { + this.push(newEntry.data); + } + } + }, this.debounceTime) + }; + this.data.set(id, newEntry); + callback(); + } + + _flush(callback: TransformCallback): void { + for (const state of this.data.values()) { + clearTimeout(state.timeout); + + if (!this.destroyed) { + this.push(state.data); + } + } + this.data.clear(); + callback(); + } + + _destroy(error: Error | null, callback: (error?: Error | null) => void): void { + for (const state of this.data.values()) { + clearTimeout(state.timeout); + // We do not push the data here, as the stream is already dead when this is called + } + this.data.clear(); + callback(error); + } +} \ No newline at end of file diff --git a/streams/tsconfig.json b/streams/tsconfig.json new file mode 100644 index 00000000..a4b5b5a1 --- /dev/null +++ b/streams/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "strict": true, + "outDir": "./dist", + "skipLibCheck": true, + "strictNullChecks": true + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/syncTime/package-lock.json b/syncTime/package-lock.json index fab83eb1..e71f987a 100644 --- a/syncTime/package-lock.json +++ b/syncTime/package-lock.json @@ -12,11 +12,11 @@ "dc-db-local": "file:../database/local", "dc-db-smdb": "file:../database/smdb", "dc-logger": "file:../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "socket.io-client": "^4.8.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -25,15 +25,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.3.0", - "@prisma/client": "^7.3.0", - "dotenv": "^17.2.3", - "pg": "^8.17.2" + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "dotenv": "^17.3.1", + "pg": "^8.20.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "@types/pg": "^8.16.0", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "@types/pg": "^8.18.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -41,14 +41,14 @@ "name": "dc-db-smdb", "version": "1.0.0", "dependencies": { - "@prisma/adapter-mariadb": "^7.3.0", - "@prisma/client": "^7.3.0", + "@prisma/adapter-mariadb": "^7.5.0", + "@prisma/client": "^7.5.0", "dc-db-local": "file:../local", - "dotenv": "^17.2.3" + "dotenv": "^17.3.1" }, "devDependencies": { - "@types/node": "^25.0.9", - "prisma": "^7.3.0", + "@types/node": "^25.5.0", + "prisma": "^7.5.0", "typescript": "^5.9.3" } }, @@ -57,12 +57,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "dotenv": "^17.2.3", - "pino": "^10.2.1", + "dotenv": "^17.3.1", + "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" } }, @@ -73,13 +73,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/dc-db-local": { @@ -112,9 +112,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -211,9 +211,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/syncTime/package.json b/syncTime/package.json index 831f5ed5..be0015a7 100644 --- a/syncTime/package.json +++ b/syncTime/package.json @@ -11,14 +11,14 @@ "license": "ISC", "description": "", "devDependencies": { - "@types/node": "^25.0.9", + "@types/node": "^25.5.0", "typescript": "^5.9.3" }, "dependencies": { "dc-db-local": "file:../database/local", "dc-db-smdb": "file:../database/smdb", "dc-logger": "file:../logger", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "socket.io-client": "^4.8.3" } } diff --git a/utils/recorder/package-lock.json b/utils/recorder/package-lock.json index 85e6dcbd..902a756c 100644 --- a/utils/recorder/package-lock.json +++ b/utils/recorder/package-lock.json @@ -12,8 +12,8 @@ "eventsource": "^4.1.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "nodemon": "^3.1.11", + "@types/node": "^25.5.0", + "nodemon": "^3.1.14", "ts-node": "^10.9.2", "typescript": "^5.9.3" } @@ -88,13 +88,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/acorn": { @@ -145,11 +146,14 @@ "license": "MIT" }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -165,14 +169,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -213,13 +219,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -388,16 +387,19 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -408,16 +410,16 @@ "license": "MIT" }, "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -591,6 +593,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -607,9 +610,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, diff --git a/utils/recorder/package.json b/utils/recorder/package.json index 5145d802..885b8d2d 100644 --- a/utils/recorder/package.json +++ b/utils/recorder/package.json @@ -15,8 +15,8 @@ "eventsource": "^4.1.0" }, "devDependencies": { - "@types/node": "^25.0.9", - "nodemon": "^3.1.11", + "@types/node": "^25.5.0", + "nodemon": "^3.1.14", "ts-node": "^10.9.2", "typescript": "^5.9.3" }