From: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:38:52 +0000 (+0200) Subject: fix: validate protocol inputs after dependency refresh (#2128) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=47e9f8f695b8a258c552b9ee1d306fbb80995780;p=e-mobility-charging-stations-simulator.git fix: validate protocol inputs after dependency refresh (#2128) * chore(deps): lock file maintenance * fix(deps): preserve strict lint compatibility * [autofix.ci] apply automated fixes * fix(deps): leave changelog to release automation * fix(deps): support typescript-eslint 8.71 * fix(ui-common): validate websocket ready state * fix(ui): validate protocol enum inputs * fix: validate protocol enum inputs * [autofix.ci] apply automated fixes * fix(types): harden dependency upgrade compatibility * [autofix.ci] apply automated fixes * fix: harden meter values and UI errors * [autofix.ci] apply automated fixes * refactor(ocpp): use meter value unit enums * refactor(ocpp): reuse canonical enum values * fix(ocpp): address dependency refresh regressions * fix(ocpp16): reject unsupported unit descriptors --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jérôme Benoit Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- diff --git a/cspell.config.yaml b/cspell.config.yaml index 706d6236..124140f9 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -2,6 +2,7 @@ version: '0.2' words: - DECI - CENTI + - Celcius - MILLI - MILLIWATT - Benoit diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3af3c879..30855231 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,16 +168,16 @@ importers: dependencies: '@mikro-orm/better-sqlite': specifier: ^6.6.14 - version: 6.6.14(@mikro-orm/core@6.6.16)(mariadb@3.4.5)(supports-color@7.2.0) + version: 6.6.14(@mikro-orm/core@6.6.17)(mariadb@3.4.5)(supports-color@7.2.0) '@mikro-orm/core': specifier: ^6.6.16 - version: 6.6.16 + version: 6.6.17 '@mikro-orm/mariadb': specifier: ^6.6.16 - version: 6.6.16(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(supports-color@7.2.0) + version: 6.6.17(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(supports-color@7.2.0) '@mikro-orm/reflection': specifier: ^6.6.16 - version: 6.6.16(@mikro-orm/core@6.6.16) + version: 6.6.17(@mikro-orm/core@6.6.17) '@modelcontextprotocol/sdk': specifier: ~1.31.0 version: 1.31.0(supports-color@7.2.0)(zod@4.6.5) @@ -232,22 +232,22 @@ importers: devDependencies: '@commitlint/cli': specifier: ^21.2.3 - version: 21.2.3(@types/node@24.19.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + version: 21.2.3(@types/node@24.19.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3) '@commitlint/config-conventional': specifier: ^21.2.3 version: 21.2.3 '@cspell/eslint-plugin': specifier: ^10.3.6 - version: 10.3.6(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + version: 10.3.6(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + version: 10.0.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) '@mikro-orm/cli': specifier: ^6.6.16 - version: 6.6.16(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) + version: 6.6.17(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) '@types/node': specifier: ^24.19.0 - version: 24.19.0 + version: 24.19.1 '@types/semver': specifier: ^7.8.0 version: 7.8.0 @@ -268,16 +268,16 @@ importers: version: 2.1.1(esbuild@0.28.2) eslint: specifier: ^10.11.0 - version: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + version: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) eslint-plugin-jsdoc: specifier: ^65.0.1 - version: 65.0.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 65.0.2(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-perfectionist: specifier: ^5.12.1 - version: 5.12.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 5.12.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-vue: specifier: ^10.11.1 - version: 10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)))(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)) + version: 10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)))(@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)) husky: specifier: ^9.1.7 version: 9.1.7 @@ -286,7 +286,7 @@ importers: version: 17.6.0 neostandard: specifier: ^0.14.0-next.1 - version: 0.14.0-next.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 0.14.0-next.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) prettier: specifier: ^3.9.9 version: 3.9.9 @@ -301,7 +301,7 @@ importers: version: 0.35.12(supports-color@7.2.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@24.19.0)(typescript@6.0.3) + version: 10.9.2(@types/node@24.19.1)(typescript@6.0.3) tsx: specifier: ^4.23.15 version: 4.23.15 @@ -313,7 +313,7 @@ importers: version: 6.0.1 vue-eslint-parser: specifier: ^10.4.1 - version: 10.4.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0) + version: 10.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0) optionalDependencies: bufferutil: specifier: ^4.1.0 @@ -375,7 +375,7 @@ importers: version: 3.5.43(typescript@6.0.3) vue-router: specifier: ^5.3.1 - version: 5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.10)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + version: 5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) vue-toast-notification: specifier: ^3.1.3 version: 3.1.3(vue@3.5.43(typescript@6.0.3)) @@ -388,10 +388,10 @@ importers: version: 30.0.0 '@vitejs/plugin-vue': specifier: ^6.0.9 - version: 6.0.9(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + version: 6.0.9(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) '@vitejs/plugin-vue-jsx': specifier: ^5.1.6 - version: 5.1.6(supports-color@7.2.0)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) + version: 5.1.6(supports-color@7.2.0)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)) '@vitest/coverage-v8': specifier: ^5.0.3 version: 5.0.3(vitest@5.0.3) @@ -406,13 +406,13 @@ importers: version: 30.1.1 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + version: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^5.0.3 - version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.0)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) + version: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) vue-tsc: - specifier: ^3.3.11 - version: 3.3.11(typescript@6.0.3) + specifier: ^3.3.12 + version: 3.3.12(typescript@6.0.3) packages: @@ -420,12 +420,12 @@ packages: resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} engines: {node: '>=4'} - '@asamuzakjp/css-color@7.0.1': - resolution: {integrity: sha512-C9duntabagkBZ1LebM7FKmphR4Q1pBclLxVbZETQV0akkFjV0ooFxo8FlvAQyKj9F6l8rEnmidgcTlpyxFYizg==} + '@asamuzakjp/css-color@7.1.3': + resolution: {integrity: sha512-1t1U8Cm3RBl9vh3RAZhjkPaduzLSvQJRToRrxpGR/eDRW99mOREleuu+mWfiFa795bUzjwht7NgphCKTs2ianQ==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - '@asamuzakjp/dom-selector@9.2.1': - resolution: {integrity: sha512-NT4s3yZLjovPpliRpTvdsdzyPjqRqiCZj9MxnarBihbaY5MbAG7DWAJcrLlhmC7xKTNCXsTELcqB8YsqpLnUFQ==} + '@asamuzakjp/dom-selector@9.2.4': + resolution: {integrity: sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} '@babel/code-frame@7.29.7': @@ -695,8 +695,8 @@ packages: '@cspell/dict-companies@3.2.13': resolution: {integrity: sha512-mBJeYzCkiiWgNQxKg7zyQOpFPwcXO4IAzXGbOEFfcQ4Q0G5Fa7Kqd8z+gvnvSK7NPWKJaFmiApC6I0cka02qmQ==} - '@cspell/dict-cpp@7.1.2': - resolution: {integrity: sha512-bkR33oE+ToyD1hwSV4XeEUB9oDkizHRX03CrQfj23tX4VUVbie5vkEHzlKVhcw/Q+fO2dtV/Vz4f6O5M4a2iZQ==} + '@cspell/dict-cpp@7.1.3': + resolution: {integrity: sha512-2d2MSh+C+xzzdqjLwtt40TdXWDR/pK0q1Q9L5zC+1EPSCIZxlN7CHbhlXWpl0JqUSdy2xaWaWucwnoGznMTneA==} '@cspell/dict-cryptocurrencies@5.0.5': resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} @@ -725,8 +725,8 @@ packages: '@cspell/dict-elixir@4.0.8': resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} - '@cspell/dict-en-common-misspellings@2.2.2': - resolution: {integrity: sha512-gu0zS7nm0jgVA2EswgWOR8od8wbNYVgcGzo0pGaDqcSkKS4WhABehLOwbyI0NIEVB+ME6SmckLVGk7g+VD1C6A==} + '@cspell/dict-en-common-misspellings@2.2.3': + resolution: {integrity: sha512-rwskWjznsDSZe3ck03BGUNT3UY9MjO1e4E9LJWACFzdDjkdM7uzro2/WfKvZTBVWVnRlzdRXPrtjkUZFI2A66A==} '@cspell/dict-en-gb-mit@3.1.29': resolution: {integrity: sha512-mInExsCFZui0f9Vg/MToWfQZzwMfPgHX8BCYdZNdXWbYz+jITXcnvssbdUkui/DVbriZVJ74Zu9bv2jOEqTCNA==} @@ -749,8 +749,8 @@ packages: '@cspell/dict-fullstack@3.2.9': resolution: {integrity: sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==} - '@cspell/dict-gaming-terms@1.1.2': - resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} + '@cspell/dict-gaming-terms@1.1.3': + resolution: {integrity: sha512-hLGzd/f+KX5B/5YxAXuQi4DYEfu6IKudtNFO/Nima3JLXD+8Xn+d0q6VdaBxkWVhGYlzow9o+dce+S9mNtgpMg==} '@cspell/dict-git@3.1.1': resolution: {integrity: sha512-XvuGVYj9YEu4EzntqyhH8XlTRyy57C6j8oxkIbUQfR2UYzxZGAP77XHl7qY7eC/40r/haDwnBaccyFR6BRmUCg==} @@ -808,8 +808,8 @@ packages: '@cspell/dict-node@5.0.9': resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} - '@cspell/dict-npm@5.2.50': - resolution: {integrity: sha512-JZhJb0wrWpqnzDBM1VG8f6zZPRCOo6YMxct0hFhgBnbOTIHQiqZf9S1BSXCRMmmQlAWqFXjKNDp+rf7ZiJcO9w==} + '@cspell/dict-npm@5.2.52': + resolution: {integrity: sha512-JttzSladJ4KVhfg9QiBM4os+wAKbet2F58wpbM5VJLnL8fAj3xwvBvSxdY64yrW6Y5wdSPHoTCLqXOKoyBJFnA==} '@cspell/dict-php@4.1.3': resolution: {integrity: sha512-41oKzl7rLVrrVZA/Gs0ESnP6KliEX7c+IwaaNmOL3PAoHasFDVEmIFeENIzuDdM12UcHT+MlLkAm3Qw/lS/ERA==} @@ -817,8 +817,8 @@ packages: '@cspell/dict-powershell@5.0.17': resolution: {integrity: sha512-khOoIdBgKRk2scnr7888pf70yJ1Tr3RGdnYcAN0MJ2vtaNRKoOTCFc13gXUkNDHwEyhMI1h9oz3KsT180F2amw==} - '@cspell/dict-public-licenses@2.0.16': - resolution: {integrity: sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==} + '@cspell/dict-public-licenses@2.0.17': + resolution: {integrity: sha512-Asdumgg3oZ4ki//b2a+beiVIsNZA5thim4t4v3lTDLY/6a8VAPmTjzC3Y2t0x7ywC/bzAhi1KW/fg8JlwHDgig==} '@cspell/dict-python@4.5.2': resolution: {integrity: sha512-CFcTDGN/ycn45WXC5ZZ06pG09WbGKMpPm1rkMuqMP98/jwjw3iiK8c0izWRd8x027n1rOZ9kJ8f9Sd+bQ22HJg==} @@ -838,8 +838,8 @@ packages: '@cspell/dict-shell@1.2.0': resolution: {integrity: sha512-PVctvT22lJ49niMiakO8xieY7ELCAzjSqhejWR7bAMb5AZ9F4WDEs+XdGMnoVHWeXq7K5rcepLPmEJb+37zzIw==} - '@cspell/dict-software-terms@5.4.5': - resolution: {integrity: sha512-5PgLJNdaFKeA6iklB0XXjmgNaoLJJqf54oRTD1raG/mgwojVlRnWE1G7x4QH9Gz07h3B3+lMDv1F57Xxs4jang==} + '@cspell/dict-software-terms@5.4.6': + resolution: {integrity: sha512-r4Xw367hTosVCAQ884nPWhwcy3Jc6YWQFkW1XBdxFGdzdCltzuNf9qdJL1AwiTWMbiRnifIqKaXN0A38WCVEbQ==} '@cspell/dict-sql@2.2.1': resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} @@ -892,40 +892,40 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@6.1.1': - resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.4.0': - resolution: {integrity: sha512-XQKj5B7QiZcHiegCOCAzcAOJdhGgWOHbbu62h5e5mkHnn8lWcfiJhllkqWmxu5zWR9jucPHuo1iTB56P033hcg==} + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-color-parser@4.2.3': - resolution: {integrity: sha512-y4LpL+lmpuyKDiEFq2PnZUVFdAjsoB/qQJod79yLNokXyW7jewi+/WJ69EfItj8A2unWtxXnGjw6LYXgXu5ZjA==} + '@csstools/css-color-parser@4.2.6': + resolution: {integrity: sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-parser-algorithms@4.0.0': - resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.2 - '@csstools/css-syntax-patches-for-csstree@1.1.14': - resolution: {integrity: sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==} + '@csstools/css-syntax-patches-for-csstree@1.1.15': + resolution: {integrity: sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: css-tree: optional: true - '@csstools/css-tokenizer@4.0.1': - resolution: {integrity: sha512-bPlN9S9O1A0euCpEWE4qnvB5YDuyYVsUTrxSgmAM1Is0j4tICHoVyOVAXfWMP/kS9ZrjvyIXWV2PmomiAXXqOw==} + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} engines: {node: '>=20.19.0'} '@dabh/diagnostics@2.0.9': @@ -1146,8 +1146,8 @@ packages: '@noble/hashes': optional: true - '@hono/node-server@2.1.1': - resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + '@hono/node-server@2.1.3': + resolution: {integrity: sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==} engines: {node: '>=20'} peerDependencies: hono: ^4 @@ -1216,13 +1216,13 @@ packages: peerDependencies: '@mikro-orm/core': ^6.0.0 - '@mikro-orm/cli@6.6.16': - resolution: {integrity: sha512-EsAr7A4wbmBib3LwUcnH1cJOYgGC8irBptt+0BiC7IaP+p2UsnQBvTBbM895rAk1rXiInFhqWKjBgSKZcDf4yQ==} + '@mikro-orm/cli@6.6.17': + resolution: {integrity: sha512-cQ9HXCVe1L+MXvlg3jNPVGkIgA4ngsH48GoGj3SfxJdrdB7W5JOLuu8dGQrc6HDUvuff78lkBpIy/XROm8t/fg==} engines: {node: '>= 18.12.0'} hasBin: true - '@mikro-orm/core@6.6.16': - resolution: {integrity: sha512-ym26fbhTti7Zh6oN1y74byoUoMEq1ahckiV7BZX5pHU/0OaUESiQA+Zar2Tv2ZFuZA9946XQ805F7/4NH6oDUQ==} + '@mikro-orm/core@6.6.17': + resolution: {integrity: sha512-LFl6s0U6evvhB2iqifGEAzY0Nl1DC4Ge2N6GcPoNEvtg3OC/0GjvIftyzXnpaANjo/IxEaY/ge5BgMLt1B729g==} engines: {node: '>= 18.12.0'} '@mikro-orm/knex@6.6.14': @@ -1241,8 +1241,8 @@ packages: mariadb: optional: true - '@mikro-orm/knex@6.6.16': - resolution: {integrity: sha512-H0fYK4vSe2mtmfBDDNsOw5Z56JDYEzbaoIo24JhkzmoGna85ddwyYzYB289DMS8Ukn7qZKhfR2pn2kQDRSJiyw==} + '@mikro-orm/knex@6.6.17': + resolution: {integrity: sha512-v65plaUKRuPsmMBXl/0h6ejqhVvS0Np4bGJSxHGOuWPONNEQHvSlt1aIpddhSYtdnzCXKV2hQNVBEaj8/wkA2Q==} engines: {node: '>= 18.12.0'} peerDependencies: '@mikro-orm/core': ^6.0.0 @@ -1257,14 +1257,14 @@ packages: mariadb: optional: true - '@mikro-orm/mariadb@6.6.16': - resolution: {integrity: sha512-4BPpR6KYzMvByp9sJg5MlXrKxoDPrFDtnYku+swzXDgcyCNZwBJisRf0hkSXd3xL7JSV5yrsg+vZY+KjKO0ECQ==} + '@mikro-orm/mariadb@6.6.17': + resolution: {integrity: sha512-JcdyFFXyMIzHgv/bTtWWGFQph4X5AdeO9f7pLNn6yDkuF4cuzgUstmCHGbs7ZLzGY5tSgxQ2bB/GbgrJwPr3zA==} engines: {node: '>= 18.12.0'} peerDependencies: '@mikro-orm/core': ^6.0.0 - '@mikro-orm/reflection@6.6.16': - resolution: {integrity: sha512-OXMhyWOag3Fyz+RyvptHKmwIPXE++5k7aFEt8JVK7YfEjqTvuB9Y7N3jQH08HQXe8VPsYs4RjAKz7DEk5E6JAA==} + '@mikro-orm/reflection@6.6.17': + resolution: {integrity: sha512-ep79EneWXx7tctmI5amZYgUT4eQv+S2/+ZGlgHU2KBP5lARNAkE1D4EaTD/hzwbt1kNDQud0TcGEgUDoaiawoA==} engines: {node: '>= 18.12.0'} peerDependencies: '@mikro-orm/core': ^6.0.0 @@ -1279,8 +1279,8 @@ packages: '@cfworker/json-schema': optional: true - '@mongodb-js/saslprep@1.5.4': - resolution: {integrity: sha512-05UC0jQsjKAOuXQ0H9Ud9vUTJpZIg+n/FinpR30tI5I8pY2inTfPOZ5OF/cg3Ce/N9MoD1xhRCeOsJtuTbFYlw==} + '@mongodb-js/saslprep@1.5.5': + resolution: {integrity: sha512-0PV4mvjGd2Q2/viDvXuoSNzTjQngHvtuWUry1vqvcK4Q/CTeO1zEKMf9set01DMocJnogl/pyNAdvyk+Pe0kQA==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1301,8 +1301,8 @@ packages: resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@oxc-project/types@0.151.0': - resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} '@parcel/watcher-android-arm64@2.6.0': resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} @@ -1396,98 +1396,98 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@rolldown/binding-android-arm-eabi@1.2.10': - resolution: {integrity: sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==} + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.10': - resolution: {integrity: sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==} + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.10': - resolution: {integrity: sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==} + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.10': - resolution: {integrity: sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==} + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.10': - resolution: {integrity: sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==} + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.10': - resolution: {integrity: sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.10': - resolution: {integrity: sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==} + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.10': - resolution: {integrity: sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==} + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.10': - resolution: {integrity: sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==} + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.10': - resolution: {integrity: sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==} + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.10': - resolution: {integrity: sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==} + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.10': - resolution: {integrity: sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==} + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.10': - resolution: {integrity: sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==} + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.10': - resolution: {integrity: sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==} + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.10': - resolution: {integrity: sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==} + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1561,14 +1561,11 @@ packages: '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - '@types/node@24.13.6': - resolution: {integrity: sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==} + '@types/node@24.19.1': + resolution: {integrity: sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==} - '@types/node@24.19.0': - resolution: {integrity: sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==} - - '@types/node@26.6.2': - resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==} + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1591,63 +1588,63 @@ packages: '@types/ws@8.18.2': resolution: {integrity: sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==} - '@typescript-eslint/eslint-plugin@8.70.1': - resolution: {integrity: sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.70.1 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.70.1': - resolution: {integrity: sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.70.1': - resolution: {integrity: sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==} + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.70.1': - resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.70.1': - resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.70.1': - resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==} + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.70.1': - resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.70.1': - resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==} + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.70.1': - resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==} + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.70.1': - resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-vue-jsx@5.1.6': @@ -1673,12 +1670,12 @@ packages: '@vitest/browser': optional: true - '@vitest/istanbul-lib-coverage@1.0.1': - resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} engines: {node: '>=22'} - '@vitest/istanbul-lib-report@1.0.1': - resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} engines: {node: '>=22'} '@vitest/mocker@5.0.3': @@ -1755,8 +1752,8 @@ packages: '@vue/devtools-shared@8.2.1': resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} - '@vue/language-core@3.3.11': - resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==} + '@vue/language-core@3.3.12': + resolution: {integrity: sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA==} '@vue/reactivity@3.5.43': resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==} @@ -1841,8 +1838,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.3.0: - resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -1917,8 +1914,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.25: - resolution: {integrity: sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==} + baseline-browser-mapping@2.11.27: + resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -1969,8 +1966,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.29.0: - resolution: {integrity: sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==} + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2011,11 +2008,11 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001810: - resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} chalk@4.1.2: @@ -2377,8 +2374,8 @@ packages: effect@3.22.2: resolution: {integrity: sha512-fm1CQXCs7GTMwpadZxwU5rnAb2bN6mbQpknq/pXBGCzSKkosNYb9k0wsoL0TfB10rSa5xOEZJjo77u/KLXSANg==} - electron-to-chromium@1.5.438: - resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==} + electron-to-chromium@1.5.444: + resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2396,8 +2393,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.25.1: - resolution: {integrity: sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==} + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} engines: {node: '>=10.13.0'} entities@7.0.1: @@ -2479,8 +2476,8 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-jsdoc@65.0.1: - resolution: {integrity: sha512-7IvB+ZS71WCw5tGKh6f6D5k55qtXKdrGTnIpQTBKWdpyZ5IfhGHfh0i6ArWlCJ4JEtz22YRk5Bqu1MvbOlpMKQ==} + eslint-plugin-jsdoc@65.0.2: + resolution: {integrity: sha512-zy1VuN7NNUmfXk15B5ZDAGTXBiye+JskSJZ22Qd5E3VlUluX/RHf8TXYTj8yradaKKJt+69aylhJS22TX/aeRA==} engines: {node: ^22.22.2 || >=24.15.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -2489,8 +2486,8 @@ packages: typescript: optional: true - eslint-plugin-n@18.3.0: - resolution: {integrity: sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==} + eslint-plugin-n@18.4.1: + resolution: {integrity: sha512-2sBReWOvCbzgkPri9M5SFmD5EbIwZLa1xz6B5RZsrI19WSFKpyx1sOMr+pdk8puCiXuEgCbErEHy1PuaUrc/4w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: '>=8.57.1' @@ -2547,8 +2544,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.11.0: - resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -2630,8 +2627,8 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - exsolve@1.1.1: - resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + exsolve@1.1.3: + resolution: {integrity: sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==} fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} @@ -2640,8 +2637,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-equals@6.0.4: - resolution: {integrity: sha512-JiBeR0k76FIahi9zAtQR7X1jgZDKLE+fs5S9ryjOGYZWZCOcm8QiwgeahkFvaboS3i6PkGBopRznHKuKhm9zpg==} + fast-equals@6.1.0: + resolution: {integrity: sha512-MDA0N5wXvmx0yR0xeOf20qVMbyaEzS2kuxhBrDrIT5w3Gap7bZ/24jtvOvbthbZFmf+7PCC8XCJKGpmhVtvbIg==} engines: {node: '>=6.0.0'} fast-glob@3.3.3: @@ -2672,8 +2669,8 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - figlet@1.11.4: - resolution: {integrity: sha512-ZU41480OncL+NVLQrT0GVnbO0COL24sLSrzksioDgunmdJNYEUxhJJZ4Y3x1csN8XXqN5OcCZTLG8lKdquNyfQ==} + figlet@1.12.0: + resolution: {integrity: sha512-Ith3WsUi0N/EqGt+0/F6XnWhT04sohhH2tT0Dq7GdUmWbMTIoCbg2Jq9qKggmTO62ST5kRCfB311EmIMbizGyA==} engines: {node: '>= 17.0.0'} hasBin: true @@ -2820,8 +2817,8 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@17.12.0: - resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} + globals@17.13.0: + resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==} engines: {node: '>=18'} globby@11.1.0: @@ -2858,8 +2855,8 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} - hono@4.13.8: - resolution: {integrity: sha512-/Gng7NfoykZl2pjukW5Z6+8Yxm3BPRf86GTbQnt0SbySkvax4fyL4H3HhY1cCpBGmiW9XDRFzRV+CXK2W8QudQ==} + hono@4.13.12: + resolution: {integrity: sha512-6E2QDAc9Ick9Sq77ZrGS/dk2WUYni91aufTw6LJKpV7w8kW5/GxVUc650FOADOmlwg3K+f7Pun6XlV+pYmW6gw==} engines: {node: '>=16.9.0'} hookable@5.5.3: @@ -2909,16 +2906,16 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.10: - resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==} + ignore@7.0.12: + resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==} engines: {node: '>= 4'} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-fresh@4.0.0: - resolution: {integrity: sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==} + import-fresh@4.0.1: + resolution: {integrity: sha512-/XFeXYgkuWgbtqPC7qPKvciXTvhtz7WMNQKQsAM1Kz+j0I5OWJDrybfiya6QtHF9oEvPkLwTHmC9+4OR/MmfIA==} engines: {node: '>=22.15'} import-meta-resolve@4.2.0: @@ -2955,8 +2952,8 @@ packages: peerDependencies: fp-ts: ^2.5.0 - ip-address@10.7.2: - resolution: {integrity: sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==} + ip-address@10.7.3: + resolution: {integrity: sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -3074,6 +3071,10 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + js-yaml@5.4.2: + resolution: {integrity: sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==} + hasBin: true + jsdoc-type-pratt-parser@9.2.2: resolution: {integrity: sha512-eFY7Rlscvv/BpY6UsVsjKwMhu1tJO/F4U/vC9g3yeRy/JqszuO1UP6Vdq+85G+3FP6fEw/sWR8a5ftPFY86csw==} engines: {node: ^22.22.2 || >=24.15.0} @@ -3335,8 +3336,8 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mikro-orm@6.6.16: - resolution: {integrity: sha512-42bvve+XSzvBpOuZeZxJsM5aW4DQnDn23E5to4cvEjUdaPX7jg1bRh+l8f/8/WJJNzG1T05V5vHSe+SHzqN87A==} + mikro-orm@6.6.17: + resolution: {integrity: sha512-AL7V8ytnDRpwUMcYwOiGNIOW7WuE8ca2hf+pw9jcpSVWwwJNu/j9K574lf0PALaJnikbzidsxmjCUrhQ0ea+sA==} engines: {node: '>= 18.12.0'} mime-db@1.54.0: @@ -3827,8 +3828,8 @@ packages: engines: {node: 20 || >=22} hasBin: true - rolldown@1.2.10: - resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==} + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3932,8 +3933,8 @@ packages: resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==} engines: {node: '>= 18'} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} sparse-bitfield@3.0.3: @@ -3966,8 +3967,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} stdin-discarder@0.3.2: resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} @@ -3985,8 +3986,8 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} engines: {node: '>=20'} string_decoder@1.3.0: @@ -4049,8 +4050,8 @@ packages: resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} engines: {node: '>=8'} - tinybench@6.1.4: - resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + tinybench@6.2.0: + resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} engines: {node: '>=20.0.0'} tinyexec@1.3.1: @@ -4061,15 +4062,15 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} engines: {node: '>=14.0.0'} - tldts-core@7.4.15: - resolution: {integrity: sha512-ERuv0p98XgSzmlSLJr8vDNxX+uATGInlN97V3R+JpYWaSqn5IG3ewWgCwczk4bkOpgth/o8Nt5FOi4B4w0n/1A==} + tldts-core@7.4.16: + resolution: {integrity: sha512-MDolfaSJtlSK5Y0A1xl3277ekubZwobpBjugknDizI9O5Rm60a1m8k4ICK+MRsCDzPygT81mp3BBf5RKDlFRfA==} - tldts@7.4.15: - resolution: {integrity: sha512-SJVBeHOxDbNoq14CvpAoA2mLEWdbldGK8nR+yumpjY5nrlZxOflcA7p1Qj1gbTGmbu9DY9qLj/bENSWhBzjxRQ==} + tldts@7.4.16: + resolution: {integrity: sha512-QwBER5KMR86IIjpIiO7H/Z3IMJPsZ1A6RKPAqzTTgOyUQUSt9FdnKcqhTaJmkY6HVrgouZHZR0ncK5QxvmnQeg==} hasBin: true to-regex-range@5.0.1: @@ -4151,8 +4152,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript-eslint@8.70.1: - resolution: {integrity: sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4176,20 +4177,17 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici-types@8.11.0: - resolution: {integrity: sha512-Ophcodxps+mlNdcccUrHXy+Pp8X7Q0ycBk3QelSNl+jBAPMh6cEB/5xs9xDL3PJVnVs2Cp//nnsJySuD9D2WFA==} + undici-types@8.11.2: + resolution: {integrity: sha512-iMVNmWZ0leK/goS6eXMizSzmm9CDWtyphwbaCms3DNLqRxDL+mMoNVcZMTyyVgXP0N+Z8neAMzDoUOUJL8veKg==} undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - undici@8.11.0: - resolution: {integrity: sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w==} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} engines: {node: '>=22.19.0'} unicorn-magic@0.3.0: @@ -4267,8 +4265,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@8.3.1: - resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} + vite@8.3.2: + resolution: {integrity: sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4355,24 +4353,24 @@ packages: resolution: {integrity: sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==} engines: {node: '>=8.0.0 || >=10.0.0'} - vscode-jsonrpc@9.0.2: - resolution: {integrity: sha512-SbQSV9yRemARxeXw6LU5sS6Zq0e9/DgCCX5yelH263ZQWukbTk8EF8fjTrr1dziasf4GwlJbvTwFnTrnQFWZXQ==} + vscode-jsonrpc@9.0.3: + resolution: {integrity: sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==} engines: {node: '>=14.0.0'} - vscode-languageserver-protocol@3.18.3: - resolution: {integrity: sha512-DF49+WeV5py4zO5hhobp60jjsDSK0lAqA0OuKBLBvp423HPWQcCbhZz3JgyfIewsEz2f8U+X75xNIFHdiXZm2w==} + vscode-languageserver-protocol@3.18.4: + resolution: {integrity: sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==} - vscode-languageserver-textdocument@1.0.14: - resolution: {integrity: sha512-EQyqJMi552E4ZTf46izQ4Fj6XquqxCySR3J5ZSD1SisMf6RfpeOWHxGBE8Gr6V0/3GHIGdAzDn8F8+1nTGCnoQ==} + vscode-languageserver-textdocument@1.0.15: + resolution: {integrity: sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==} - vscode-languageserver-types@3.18.3: - resolution: {integrity: sha512-XIlzJ7Qp/jzSI1ds7/FwPAWrPeTZA7pAtlW4hdJ1J6xXWJL6dR9QYnDhJOdLzdKhUQ5Mm6mvUMw+3DcOQQasPw==} + vscode-languageserver-types@3.18.4: + resolution: {integrity: sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==} vscode-uri@3.2.0: resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} - vue-component-type-helpers@3.3.11: - resolution: {integrity: sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==} + vue-component-type-helpers@3.3.12: + resolution: {integrity: sha512-HwCqmgTfLFskQc4P5TRnbduCMyPat2Ezu0elLWOGn0dD/6BKHP15B5bxce6aDi+E4kblPtru8amIBQzu0Di3qQ==} vue-eslint-parser@10.4.1: resolution: {integrity: sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==} @@ -4404,8 +4402,8 @@ packages: peerDependencies: vue: ^3.0 - vue-tsc@3.3.11: - resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==} + vue-tsc@3.3.12: + resolution: {integrity: sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -4587,15 +4585,15 @@ snapshots: '@arr/every@1.0.1': {} - '@asamuzakjp/css-color@7.0.1': + '@asamuzakjp/css-color@7.1.3': dependencies: - '@csstools/css-calc': 3.4.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1) - '@csstools/css-color-parser': 4.2.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) - '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 lru-cache: 11.5.3 - '@asamuzakjp/dom-selector@9.2.1': + '@asamuzakjp/dom-selector@9.2.4': dependencies: bidi-js: 1.1.0 css-tree: 3.2.1 @@ -4646,7 +4644,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.29.0 + browserslist: 4.29.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -4794,12 +4792,12 @@ snapshots: '@colors/colors@1.6.1': {} - '@commitlint/cli@21.2.3(@types/node@24.19.0)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + '@commitlint/cli@21.2.3(@types/node@24.19.1)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': dependencies: '@commitlint/config-conventional': 21.2.3 '@commitlint/format': 21.2.3 '@commitlint/lint': 21.2.3 - '@commitlint/load': 21.2.3(@types/node@24.19.0)(typescript@6.0.3) + '@commitlint/load': 21.2.3(@types/node@24.19.1)(typescript@6.0.3) '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.2) '@commitlint/types': 21.2.3 tinyexec: 1.3.1 @@ -4844,14 +4842,14 @@ snapshots: '@commitlint/rules': 21.2.3 '@commitlint/types': 21.2.3 - '@commitlint/load@21.2.3(@types/node@24.19.0)(typescript@6.0.3)': + '@commitlint/load@21.2.3(@types/node@24.19.1)(typescript@6.0.3)': dependencies: '@commitlint/config-validator': 21.2.3 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.3 '@commitlint/types': 21.2.3 cosmiconfig: 9.0.2(typescript@6.0.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@24.19.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@24.19.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) es-toolkit: 1.52.0 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -4920,7 +4918,7 @@ snapshots: '@cspell/dict-aws': 4.0.17 '@cspell/dict-bash': 4.2.3 '@cspell/dict-companies': 3.2.13 - '@cspell/dict-cpp': 7.1.2 + '@cspell/dict-cpp': 7.1.3 '@cspell/dict-cryptocurrencies': 5.0.5 '@cspell/dict-csharp': 4.0.8 '@cspell/dict-css': 4.1.2 @@ -4930,7 +4928,7 @@ snapshots: '@cspell/dict-docker': 1.1.17 '@cspell/dict-dotnet': 5.0.15 '@cspell/dict-elixir': 4.0.8 - '@cspell/dict-en-common-misspellings': 2.2.2 + '@cspell/dict-en-common-misspellings': 2.2.3 '@cspell/dict-en-gb-mit': 3.1.29 '@cspell/dict-en_us': 4.4.40 '@cspell/dict-filetypes': 3.0.18 @@ -4938,7 +4936,7 @@ snapshots: '@cspell/dict-fonts': 4.0.6 '@cspell/dict-fsharp': 1.1.1 '@cspell/dict-fullstack': 3.2.9 - '@cspell/dict-gaming-terms': 1.1.2 + '@cspell/dict-gaming-terms': 1.1.3 '@cspell/dict-git': 3.1.1 '@cspell/dict-golang': 6.0.29 '@cspell/dict-google': 1.0.9 @@ -4956,17 +4954,17 @@ snapshots: '@cspell/dict-markdown': 2.0.18(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.16)(@cspell/dict-typescript@3.2.3) '@cspell/dict-monkeyc': 1.1.2 '@cspell/dict-node': 5.0.9 - '@cspell/dict-npm': 5.2.50 + '@cspell/dict-npm': 5.2.52 '@cspell/dict-php': 4.1.3 '@cspell/dict-powershell': 5.0.17 - '@cspell/dict-public-licenses': 2.0.16 + '@cspell/dict-public-licenses': 2.0.17 '@cspell/dict-python': 4.5.2 '@cspell/dict-r': 2.1.1 '@cspell/dict-ruby': 5.1.4 '@cspell/dict-rust': 4.1.2 '@cspell/dict-scala': 5.0.11 '@cspell/dict-shell': 1.2.0 - '@cspell/dict-software-terms': 5.4.5 + '@cspell/dict-software-terms': 5.4.6 '@cspell/dict-sql': 2.2.1 '@cspell/dict-svelte': 1.0.7 '@cspell/dict-swift': 2.0.6 @@ -4999,7 +4997,7 @@ snapshots: '@cspell/dict-companies@3.2.13': {} - '@cspell/dict-cpp@7.1.2': {} + '@cspell/dict-cpp@7.1.3': {} '@cspell/dict-cryptocurrencies@5.0.5': {} @@ -5019,7 +5017,7 @@ snapshots: '@cspell/dict-elixir@4.0.8': {} - '@cspell/dict-en-common-misspellings@2.2.2': {} + '@cspell/dict-en-common-misspellings@2.2.3': {} '@cspell/dict-en-gb-mit@3.1.29': {} @@ -5035,7 +5033,7 @@ snapshots: '@cspell/dict-fullstack@3.2.9': {} - '@cspell/dict-gaming-terms@1.1.2': {} + '@cspell/dict-gaming-terms@1.1.3': {} '@cspell/dict-git@3.1.1': {} @@ -5076,13 +5074,13 @@ snapshots: '@cspell/dict-node@5.0.9': {} - '@cspell/dict-npm@5.2.50': {} + '@cspell/dict-npm@5.2.52': {} '@cspell/dict-php@4.1.3': {} '@cspell/dict-powershell@5.0.17': {} - '@cspell/dict-public-licenses@2.0.16': {} + '@cspell/dict-public-licenses@2.0.17': {} '@cspell/dict-python@4.5.2': dependencies: @@ -5098,7 +5096,7 @@ snapshots: '@cspell/dict-shell@1.2.0': {} - '@cspell/dict-software-terms@5.4.5': {} + '@cspell/dict-software-terms@5.4.6': {} '@cspell/dict-sql@2.2.1': {} @@ -5119,12 +5117,12 @@ snapshots: '@cspell/url': 10.3.6 import-meta-resolve: 4.2.0 - '@cspell/eslint-plugin@10.3.6(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))': + '@cspell/eslint-plugin@10.3.6(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))': dependencies: '@cspell/cspell-types': 10.3.6 '@cspell/url': 10.3.6 cspell-lib: 10.3.6 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) synckit: 0.11.13 '@cspell/filetypes@10.3.6': {} @@ -5139,29 +5137,29 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@6.1.1': {} + '@csstools/color-helpers@6.1.2': {} - '@csstools/css-calc@3.4.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1)': + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) - '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 - '@csstools/css-color-parser@4.2.3(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1)': + '@csstools/css-color-parser@4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/color-helpers': 6.1.1 - '@csstools/css-calc': 3.4.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1))(@csstools/css-tokenizer@4.0.1) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.1) - '@csstools/css-tokenizer': 4.0.1 + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 - '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.1)': + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/css-tokenizer': 4.0.1 + '@csstools/css-tokenizer': 4.0.2 - '@csstools/css-syntax-patches-for-csstree@1.1.14(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.15(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 - '@csstools/css-tokenizer@4.0.1': {} + '@csstools/css-tokenizer@4.0.2': {} '@dabh/diagnostics@2.0.9': dependencies: @@ -5174,7 +5172,7 @@ snapshots: '@es-joy/jsdoccomment@0.98.0': dependencies: '@types/estree': 1.0.9 - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/types': 8.71.0 comment-parser: 1.4.9 esquery: 1.7.0 jsdoc-type-pratt-parser: 9.2.2 @@ -5259,9 +5257,9 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))': dependencies: - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5282,9 +5280,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))': optionalDependencies: - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -5295,9 +5293,9 @@ snapshots: '@exodus/bytes@1.16.0': {} - '@hono/node-server@2.1.1(hono@4.13.8)': + '@hono/node-server@2.1.3(hono@4.13.12)': dependencies: - hono: 4.13.8 + hono: 4.13.12 '@humanfs/core@0.19.2': dependencies: @@ -5324,7 +5322,7 @@ snapshots: '@jercle/yargonaut@1.1.5': dependencies: chalk: 4.1.2 - figlet: 1.11.4 + figlet: 1.12.0 parent-require: 1.0.0 '@jridgewell/gen-mapping@0.3.13': @@ -5359,10 +5357,10 @@ snapshots: '@keyv/serialize@1.1.1': {} - '@mikro-orm/better-sqlite@6.6.14(@mikro-orm/core@6.6.16)(mariadb@3.4.5)(supports-color@7.2.0)': + '@mikro-orm/better-sqlite@6.6.14(@mikro-orm/core@6.6.17)(mariadb@3.4.5)(supports-color@7.2.0)': dependencies: - '@mikro-orm/core': 6.6.16 - '@mikro-orm/knex': 6.6.14(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) + '@mikro-orm/core': 6.6.17 + '@mikro-orm/knex': 6.6.14(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) better-sqlite3: 11.10.0 fs-extra: 11.3.3 sqlstring-sqlite: 0.1.1 @@ -5378,11 +5376,11 @@ snapshots: - supports-color - tedious - '@mikro-orm/cli@6.6.16(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': + '@mikro-orm/cli@6.6.17(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': dependencies: '@jercle/yargonaut': 1.1.5 - '@mikro-orm/core': 6.6.16 - '@mikro-orm/knex': 6.6.16(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) + '@mikro-orm/core': 6.6.17 + '@mikro-orm/knex': 6.6.17(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) fs-extra: 11.3.3 tsconfig-paths: 4.2.0 yargs: 17.7.2 @@ -5399,19 +5397,19 @@ snapshots: - supports-color - tedious - '@mikro-orm/core@6.6.16': + '@mikro-orm/core@6.6.17': dependencies: dataloader: 2.2.3 dotenv: 17.3.1 esprima: 4.0.1 fs-extra: 11.3.3 globby: 11.1.0 - mikro-orm: 6.6.16 + mikro-orm: 6.6.17 reflect-metadata: 0.2.2 - '@mikro-orm/knex@6.6.14(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': + '@mikro-orm/knex@6.6.14(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': dependencies: - '@mikro-orm/core': 6.6.16 + '@mikro-orm/core': 6.6.17 fs-extra: 11.3.3 knex: 3.2.10(better-sqlite3@11.10.0)(supports-color@7.2.0) sqlstring: 2.3.3 @@ -5428,9 +5426,9 @@ snapshots: - supports-color - tedious - '@mikro-orm/knex@6.6.16(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': + '@mikro-orm/knex@6.6.17(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0)': dependencies: - '@mikro-orm/core': 6.6.16 + '@mikro-orm/core': 6.6.17 fs-extra: 11.3.3 knex: 3.2.10(better-sqlite3@11.10.0)(supports-color@7.2.0) sqlstring: 2.3.3 @@ -5447,10 +5445,10 @@ snapshots: - supports-color - tedious - '@mikro-orm/mariadb@6.6.16(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(supports-color@7.2.0)': + '@mikro-orm/mariadb@6.6.17(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(supports-color@7.2.0)': dependencies: - '@mikro-orm/core': 6.6.16 - '@mikro-orm/knex': 6.6.16(@mikro-orm/core@6.6.16)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) + '@mikro-orm/core': 6.6.17 + '@mikro-orm/knex': 6.6.17(@mikro-orm/core@6.6.17)(better-sqlite3@11.10.0)(mariadb@3.4.5)(supports-color@7.2.0) mariadb: 3.4.5 transitivePeerDependencies: - better-sqlite3 @@ -5464,15 +5462,15 @@ snapshots: - supports-color - tedious - '@mikro-orm/reflection@6.6.16(@mikro-orm/core@6.6.16)': + '@mikro-orm/reflection@6.6.17(@mikro-orm/core@6.6.17)': dependencies: - '@mikro-orm/core': 6.6.16 + '@mikro-orm/core': 6.6.17 globby: 11.1.0 ts-morph: 27.0.2 '@modelcontextprotocol/sdk@1.31.0(supports-color@7.2.0)(zod@4.6.5)': dependencies: - '@hono/node-server': 2.1.1(hono@4.13.8) + '@hono/node-server': 2.1.3(hono@4.13.12) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -5482,7 +5480,7 @@ snapshots: eventsource-parser: 3.1.1 express: 5.2.1(supports-color@7.2.0) express-rate-limit: 8.7.0(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0) - hono: 4.13.8 + hono: 4.13.12 jose: 6.2.12 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -5492,7 +5490,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@mongodb-js/saslprep@1.5.4': + '@mongodb-js/saslprep@1.5.5': dependencies: sparse-bitfield: 3.0.3 @@ -5512,7 +5510,7 @@ snapshots: '@opentelemetry/api@1.9.1': {} - '@oxc-project/types@0.151.0': {} + '@oxc-project/types@0.152.0': {} '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -5576,49 +5574,49 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@rolldown/binding-android-arm-eabi@1.2.10': + '@rolldown/binding-android-arm-eabi@1.2.12': optional: true - '@rolldown/binding-android-arm64@1.2.10': + '@rolldown/binding-android-arm64@1.2.12': optional: true - '@rolldown/binding-darwin-arm64@1.2.10': + '@rolldown/binding-darwin-arm64@1.2.12': optional: true - '@rolldown/binding-darwin-x64@1.2.10': + '@rolldown/binding-darwin-x64@1.2.12': optional: true - '@rolldown/binding-freebsd-x64@1.2.10': + '@rolldown/binding-freebsd-x64@1.2.12': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.10': + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.10': + '@rolldown/binding-linux-arm64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.10': + '@rolldown/binding-linux-arm64-musl@1.2.12': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.10': + '@rolldown/binding-linux-ppc64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.10': + '@rolldown/binding-linux-s390x-gnu@1.2.12': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.10': + '@rolldown/binding-linux-x64-gnu@1.2.12': optional: true - '@rolldown/binding-linux-x64-musl@1.2.10': + '@rolldown/binding-linux-x64-musl@1.2.12': optional: true - '@rolldown/binding-openharmony-arm64@1.2.10': + '@rolldown/binding-openharmony-arm64@1.2.12': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.10': + '@rolldown/binding-win32-arm64-msvc@1.2.12': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.10': + '@rolldown/binding-win32-x64-msvc@1.2.12': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -5638,11 +5636,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - '@typescript-eslint/types': 8.70.1 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + '@typescript-eslint/types': 8.71.0 + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -5679,24 +5677,20 @@ snapshots: '@types/jsdom@30.0.0': dependencies: - '@types/node': 24.13.6 + '@types/node': 24.19.1 '@types/tough-cookie': 4.0.5 parse5: 8.0.1 - undici-types: 8.11.0 + undici-types: 8.11.2 '@types/json-schema@7.0.15': {} '@types/minimatch@3.0.5': {} - '@types/node@24.13.6': - dependencies: - undici-types: 7.18.2 - - '@types/node@24.19.0': + '@types/node@24.19.1': dependencies: undici-types: 7.24.6 - '@types/node@26.6.2': + '@types/node@26.6.4': dependencies: undici-types: 8.9.0 @@ -5716,87 +5710,87 @@ snapshots: '@types/ws@8.18.2': dependencies: - '@types/node': 24.19.0 + '@types/node': 24.19.1 - '@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.1 - '@typescript-eslint/type-utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.1 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) - ignore: 7.0.10 + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) + ignore: 7.0.12 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.70.1 - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.1 + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.70.1(supports-color@7.2.0)(typescript@5.9.3)': + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3) - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.70.1(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3) - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.70.1': + '@typescript-eslint/scope-manager@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/visitor-keys': 8.70.1 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 - '@typescript-eslint/tsconfig-utils@8.70.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.70.1(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.70.1': {} + '@typescript-eslint/types@8.71.0': {} - '@typescript-eslint/typescript-estree@8.70.1(supports-color@7.2.0)(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.70.1(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3) - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/visitor-keys': 8.70.1 + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 @@ -5806,12 +5800,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.70.1(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.70.1(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@6.0.3) - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/visitor-keys': 8.70.1 + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 @@ -5821,66 +5815,66 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.70.1 - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.70.1': + '@typescript-eslint/visitor-keys@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-vue-jsx@5.1.6(supports-color@7.2.0)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))': + '@vitejs/plugin-vue-jsx@5.1.6(supports-color@7.2.0)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))': dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@rolldown/pluginutils': 1.0.1 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) vue: 3.5.43(typescript@6.0.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.9(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.9(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) vue: 3.5.43(typescript@6.0.3) '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/istanbul-lib-report': 1.0.1 + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 ast-v8-to-istanbul: 1.0.7 magicast: 0.5.5 obug: 2.2.1 - std-env: 4.2.0 - tinyrainbow: 3.1.1 - vitest: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.0)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) - '@vitest/istanbul-lib-coverage@1.0.1': {} + '@vitest/istanbul-lib-coverage@1.0.2': {} - '@vitest/istanbul-lib-report@1.0.1': + '@vitest/istanbul-lib-report@1.0.2': dependencies: - '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@5.0.3(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))': + '@vitest/mocker@5.0.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.3 estree-walker: 3.0.3 magic-string: 1.4.2 optionalDependencies: - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) '@vitest/spy@5.0.3': {} @@ -5943,7 +5937,7 @@ snapshots: '@vue/shared': 3.5.43 entities: 7.0.1 estree-walker: 2.0.2 - source-map-js: 1.2.1 + source-map-js: 1.2.2 '@vue/compiler-dom@3.5.43': dependencies: @@ -5960,7 +5954,7 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.28 - source-map-js: 1.2.1 + source-map-js: 1.2.2 '@vue/compiler-ssr@3.5.43': dependencies: @@ -5980,7 +5974,7 @@ snapshots: '@vue/devtools-shared@8.2.1': {} - '@vue/language-core@3.3.11': + '@vue/language-core@3.3.12': dependencies: '@volar/language-core': 2.4.28 '@vue/compiler-dom': 3.5.43 @@ -6019,7 +6013,7 @@ snapshots: '@vue/compiler-dom': 3.5.43 js-beautify: 2.0.3 vue: 3.5.43(typescript@6.0.3) - vue-component-type-helpers: 3.3.11 + vue-component-type-helpers: 3.3.12 optionalDependencies: '@vue/server-renderer': 3.5.43 @@ -6072,7 +6066,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.3.0: {} + ansi-regex@6.4.0: {} ansi-styles@4.3.0: dependencies: @@ -6132,7 +6126,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.11.25: {} + baseline-browser-mapping@2.11.27: {} basic-ftp@6.2.1: {} @@ -6194,13 +6188,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.29.0: + browserslist@4.29.3: dependencies: - baseline-browser-mapping: 2.11.25 - caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.438 + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.444 node-releases: 2.0.57 - update-browserslist-db: 1.3.3(browserslist@4.29.0) + update-browserslist-db: 1.3.3(browserslist@4.29.3) bson@7.3.3: {} @@ -6240,9 +6234,9 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001814: {} - chai@6.2.2: {} + chai@6.3.0: {} chalk@4.1.2: dependencies: @@ -6405,9 +6399,9 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.3.0(@types/node@24.19.0)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@24.19.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: - '@types/node': 24.19.0 + '@types/node': 24.19.1 cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 typescript: 6.0.3 @@ -6455,7 +6449,7 @@ snapshots: '@cspell/cspell-pipe': 10.3.6 '@cspell/cspell-types': 10.3.6 cspell-trie-lib: 10.3.6(@cspell/cspell-types@10.3.6) - fast-equals: 6.0.4 + fast-equals: 6.1.0 cspell-glob@10.3.6: dependencies: @@ -6492,9 +6486,9 @@ snapshots: cspell-trie-lib: 10.3.6(@cspell/cspell-types@10.3.6) env-paths: 4.0.0 gensequence: 8.0.8 - import-fresh: 4.0.0 + import-fresh: 4.0.1 resolve-from: 5.0.0 - vscode-languageserver-textdocument: 1.0.14 + vscode-languageserver-textdocument: 1.0.15 vscode-uri: 3.2.0 xdg-basedir: 5.1.0 @@ -6505,7 +6499,7 @@ snapshots: css-tree@3.2.1: dependencies: mdn-data: 2.27.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 cssesc@3.0.0: {} @@ -6633,7 +6627,7 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.438: {} + electron-to-chromium@1.5.444: {} emoji-regex@10.6.0: {} @@ -6647,7 +6641,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.25.1: + enhanced-resolve@5.26.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -6729,28 +6723,28 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)): + eslint-compat-utils@0.5.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)): dependencies: - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) semver: 7.8.5 - eslint-plugin-es-x@7.8.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)): + eslint-plugin-es-x@7.8.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) - eslint-compat-utils: 0.5.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) + eslint-compat-utils: 0.5.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) - eslint-plugin-jsdoc@65.0.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-jsdoc@65.0.2(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@es-joy/jsdoccomment': 0.98.0 '@es-joy/resolve.exports': 1.2.0 - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) are-docs-informative: 0.1.1 comment-parser: 1.4.9 debug: 4.4.3(supports-color@7.2.0) escape-string-regexp: 5.0.0 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -6764,49 +6758,50 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-n@18.3.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(typescript@6.0.3): + eslint-plugin-n@18.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(typescript@6.0.3): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - enhanced-resolve: 5.25.1 - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) - eslint-plugin-es-x: 7.8.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + enhanced-resolve: 5.26.0 + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) + eslint-plugin-es-x: 7.8.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) get-tsconfig: 4.14.3 globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 + js-yaml: 5.4.2 semver: 7.8.5 optionalDependencies: typescript: 6.0.3 - eslint-plugin-perfectionist@5.12.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-perfectionist@5.12.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/types': 8.70.1 - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) natural-orderby: 5.0.0 optionalDependencies: - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-promise@7.3.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)): + eslint-plugin-promise@7.3.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) - eslint-plugin-vue@10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)))(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)): + eslint-plugin-vue@10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)))(@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.6 semver: 7.8.5 - vue-eslint-parser: 10.4.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0) + vue-eslint-parser: 10.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0) xml-name-validator: 5.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - '@typescript-eslint/parser': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-scope@9.1.2: dependencies: @@ -6821,9 +6816,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0): + eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.7.0 @@ -6912,7 +6907,7 @@ snapshots: dependencies: debug: 4.4.3(supports-color@7.2.0) express: 5.2.1(supports-color@7.2.0) - ip-address: 10.7.2 + ip-address: 10.7.3 transitivePeerDependencies: - supports-color @@ -6949,7 +6944,7 @@ snapshots: transitivePeerDependencies: - supports-color - exsolve@1.1.1: {} + exsolve@1.1.3: {} fast-check@3.23.2: dependencies: @@ -6957,7 +6952,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-equals@6.0.4: {} + fast-equals@6.1.0: {} fast-glob@3.3.3: dependencies: @@ -6983,7 +6978,7 @@ snapshots: fecha@4.2.3: {} - figlet@1.11.4: + figlet@1.12.0: dependencies: commander: 14.0.3 @@ -7149,7 +7144,7 @@ snapshots: globals@15.15.0: {} - globals@17.12.0: {} + globals@17.13.0: {} globby@11.1.0: dependencies: @@ -7182,7 +7177,7 @@ snapshots: dependencies: parse-passwd: 1.0.0 - hono@4.13.8: {} + hono@4.13.12: {} hookable@5.5.3: {} @@ -7226,14 +7221,14 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.10: {} + ignore@7.0.12: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-fresh@4.0.0: {} + import-fresh@4.0.1: {} import-meta-resolve@4.2.0: {} @@ -7258,7 +7253,7 @@ snapshots: dependencies: fp-ts: 2.16.11 - ip-address@10.7.2: {} + ip-address@10.7.3: {} ipaddr.js@1.9.1: {} @@ -7341,17 +7336,21 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@5.4.2: + dependencies: + argparse: 2.0.1 + jsdoc-type-pratt-parser@9.2.2: dependencies: '@types/estree': 1.0.9 - '@types/node': 26.6.2 + '@types/node': 26.6.4 jsdom@30.1.1: dependencies: - '@asamuzakjp/css-color': 7.0.1 - '@asamuzakjp/dom-selector': 9.2.1 + '@asamuzakjp/css-color': 7.1.3 + '@asamuzakjp/dom-selector': 9.2.4 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.14(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.15(css-tree@3.2.1) '@exodus/bytes': 1.16.0 css-tree: 3.2.1 data-urls: 7.0.0 @@ -7362,7 +7361,7 @@ snapshots: parse5: 8.0.1 saxes: 6.0.0 tough-cookie: 6.0.2 - undici: 8.11.0 + undici: 8.11.2 w3c-xmlserializer: 6.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -7542,14 +7541,14 @@ snapshots: dependencies: '@babel/parser': 7.29.9 '@babel/types': 7.29.8 - source-map-js: 1.2.1 + source-map-js: 1.2.2 make-error@1.3.6: {} mariadb@3.4.5: dependencies: '@types/geojson': 7946.0.16 - '@types/node': 24.13.6 + '@types/node': 24.19.1 denque: 2.1.0 iconv-lite: 0.6.3 lru-cache: 10.4.3 @@ -7577,7 +7576,7 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - mikro-orm@6.6.16: {} + mikro-orm@6.6.17: {} mime-db@1.54.0: {} @@ -7635,7 +7634,7 @@ snapshots: mongodb@7.7.0: dependencies: - '@mongodb-js/saslprep': 1.5.4 + '@mongodb-js/saslprep': 1.5.5 bson: 7.3.3 mongodb-connection-string-url: 7.0.2 @@ -7675,17 +7674,17 @@ snapshots: dependencies: content-type: 2.1.0 - neostandard@0.14.0-next.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + neostandard@0.14.0-next.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@humanwhocodes/gitignore-to-minimatch': 1.0.2 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) - eslint-plugin-n: 18.3.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(typescript@6.0.3) - eslint-plugin-promise: 7.3.0(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0)) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) + eslint-plugin-n: 18.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(typescript@6.0.3) + eslint-plugin-promise: 7.3.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0)) find-up: 8.0.0 - globals: 17.12.0 + globals: 17.13.0 peowly: 2.0.1 - typescript-eslint: 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + typescript-eslint: 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) transitivePeerDependencies: - supports-color - ts-declaration-location @@ -7762,7 +7761,7 @@ snapshots: is-unicode-supported: 2.1.0 log-symbols: 7.0.1 stdin-discarder: 0.3.2 - string-width: 8.2.2 + string-width: 8.3.0 p-limit@3.1.0: dependencies: @@ -7859,7 +7858,7 @@ snapshots: pkg-types@2.3.3: dependencies: confbox: 0.3.1 - exsolve: 1.1.1 + exsolve: 1.1.3 pathe: 2.0.3 please-upgrade-node@3.2.0: @@ -7882,7 +7881,7 @@ snapshots: dependencies: nanoid: 3.3.19 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 prebuild-install@7.1.3: dependencies: @@ -8013,26 +8012,26 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 - rolldown@1.2.10: + rolldown@1.2.12: dependencies: - '@oxc-project/types': 0.151.0 + '@oxc-project/types': 0.152.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.10 - '@rolldown/binding-android-arm64': 1.2.10 - '@rolldown/binding-darwin-arm64': 1.2.10 - '@rolldown/binding-darwin-x64': 1.2.10 - '@rolldown/binding-freebsd-x64': 1.2.10 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.10 - '@rolldown/binding-linux-arm64-gnu': 1.2.10 - '@rolldown/binding-linux-arm64-musl': 1.2.10 - '@rolldown/binding-linux-ppc64-gnu': 1.2.10 - '@rolldown/binding-linux-s390x-gnu': 1.2.10 - '@rolldown/binding-linux-x64-gnu': 1.2.10 - '@rolldown/binding-linux-x64-musl': 1.2.10 - '@rolldown/binding-openharmony-arm64': 1.2.10 - '@rolldown/binding-win32-arm64-msvc': 1.2.10 - '@rolldown/binding-win32-x64-msvc': 1.2.10 + '@rolldown/binding-android-arm-eabi': 1.2.12 + '@rolldown/binding-android-arm64': 1.2.12 + '@rolldown/binding-darwin-arm64': 1.2.12 + '@rolldown/binding-darwin-x64': 1.2.12 + '@rolldown/binding-freebsd-x64': 1.2.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.12 + '@rolldown/binding-linux-arm64-gnu': 1.2.12 + '@rolldown/binding-linux-arm64-musl': 1.2.12 + '@rolldown/binding-linux-ppc64-gnu': 1.2.12 + '@rolldown/binding-linux-s390x-gnu': 1.2.12 + '@rolldown/binding-linux-x64-gnu': 1.2.12 + '@rolldown/binding-linux-x64-musl': 1.2.12 + '@rolldown/binding-openharmony-arm64': 1.2.12 + '@rolldown/binding-win32-arm64-msvc': 1.2.12 + '@rolldown/binding-win32-x64-msvc': 1.2.12 router@2.2.0(supports-color@7.2.0): dependencies: @@ -8150,7 +8149,7 @@ snapshots: skott@0.35.12(supports-color@7.2.0): dependencies: '@parcel/watcher': 2.6.0 - '@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@5.9.3) commander: 11.1.0 compression: 1.8.2(supports-color@7.2.0) depcheck: 1.4.7(supports-color@7.2.0) @@ -8180,7 +8179,7 @@ snapshots: smol-toml@1.9.0: {} - source-map-js@1.2.1: {} + source-map-js@1.2.2: {} sparse-bitfield@3.0.3: dependencies: @@ -8205,7 +8204,7 @@ snapshots: statuses@2.0.2: {} - std-env@4.2.0: {} + std-env@4.3.0: {} stdin-discarder@0.3.2: {} @@ -8223,7 +8222,7 @@ snapshots: get-east-asian-width: 1.7.0 strip-ansi: 7.2.0 - string-width@8.2.2: + string-width@8.3.0: dependencies: get-east-asian-width: 1.7.0 strip-ansi: 7.2.0 @@ -8238,7 +8237,7 @@ snapshots: strip-ansi@7.2.0: dependencies: - ansi-regex: 6.3.0 + ansi-regex: 6.4.0 strip-bom@3.0.0: {} @@ -8289,7 +8288,7 @@ snapshots: tildify@2.0.0: {} - tinybench@6.1.4: {} + tinybench@6.2.0: {} tinyexec@1.3.1: {} @@ -8298,13 +8297,13 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinyrainbow@3.1.1: {} + tinyrainbow@3.2.0: {} - tldts-core@7.4.15: {} + tldts-core@7.4.16: {} - tldts@7.4.15: + tldts@7.4.16: dependencies: - tldts-core: 7.4.15 + tldts-core: 7.4.16 to-regex-range@5.0.1: dependencies: @@ -8321,7 +8320,7 @@ snapshots: tough-cookie@6.0.2: dependencies: - tldts: 7.4.15 + tldts: 7.4.16 tr46@5.1.1: dependencies: @@ -8350,14 +8349,14 @@ snapshots: '@ts-morph/common': 0.28.1 code-block-writer: 13.0.3 - ts-node@10.9.2(@types/node@24.19.0)(typescript@6.0.3): + ts-node@10.9.2(@types/node@24.19.1)(typescript@6.0.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.13 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.19.0 + '@types/node': 24.19.1 acorn: 8.18.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -8394,13 +8393,13 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typescript-eslint@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.70.1(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -8408,7 +8407,7 @@ snapshots: typescript-language-server@6.0.1: dependencies: vscode-jsonrpc: 5.0.1 - vscode-languageserver-protocol: 3.18.3 + vscode-languageserver-protocol: 3.18.4 typescript@5.9.3: {} @@ -8416,15 +8415,13 @@ snapshots: ufo@1.6.4: {} - undici-types@7.18.2: {} - undici-types@7.24.6: {} - undici-types@8.11.0: {} + undici-types@8.11.2: {} undici-types@8.9.0: {} - undici@8.11.0: {} + undici@8.11.2: {} unicorn-magic@0.3.0: {} @@ -8437,19 +8434,19 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.7 - unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.10)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)): + unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: esbuild: 0.28.2 - rolldown: 1.2.10 - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + rolldown: 1.2.12 + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) - update-browserslist-db@1.3.3(browserslist@4.29.0): + update-browserslist-db@1.3.3(browserslist@4.29.3): dependencies: - browserslist: 4.29.0 + browserslist: 4.29.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -8468,40 +8465,40 @@ snapshots: vary@1.1.2: {} - vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1): + vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 postcss: 8.5.28 - rolldown: 1.2.10 + rolldown: 1.2.12 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.19.0 + '@types/node': 24.19.1 esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.23.15 yaml: 2.9.1 - vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.0)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)): + vitest@5.0.3(@opentelemetry/api@1.9.1)(@types/node@24.19.1)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.3(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) - chai: 6.2.2 + '@vitest/mocker': 5.0.3(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 magic-string: 1.4.2 obug: 2.2.1 picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 + std-env: 4.3.0 + tinybench: 6.2.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 24.19.0 + '@types/node': 24.19.1 '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) jsdom: 30.1.1 transitivePeerDependencies: @@ -8509,25 +8506,25 @@ snapshots: vscode-jsonrpc@5.0.1: {} - vscode-jsonrpc@9.0.2: {} + vscode-jsonrpc@9.0.3: {} - vscode-languageserver-protocol@3.18.3: + vscode-languageserver-protocol@3.18.4: dependencies: - vscode-jsonrpc: 9.0.2 - vscode-languageserver-types: 3.18.3 + vscode-jsonrpc: 9.0.3 + vscode-languageserver-types: 3.18.4 - vscode-languageserver-textdocument@1.0.14: {} + vscode-languageserver-textdocument@1.0.15: {} - vscode-languageserver-types@3.18.3: {} + vscode-languageserver-types@3.18.4: {} vscode-uri@3.2.0: {} - vue-component-type-helpers@3.3.11: {} + vue-component-type-helpers@3.3.12: {} - vue-eslint-parser@10.4.1(eslint@10.11.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0): + vue-eslint-parser@10.4.1(eslint@10.12.0(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(jiti@2.6.1)(supports-color@7.2.0) + eslint: 10.12.0(jiti@2.6.1)(supports-color@7.2.0) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 @@ -8536,7 +8533,7 @@ snapshots: transitivePeerDependencies: - supports-color - vue-router@5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.10)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)): + vue-router@5.3.1(@vue/compiler-sfc@3.5.43)(esbuild@0.28.2)(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3)): dependencies: '@vue-macros/common': 3.1.4(vue@3.5.43(typescript@6.0.3)) '@vue/devtools-api': 8.2.1 @@ -8552,12 +8549,12 @@ snapshots: picomatch: 4.0.7 scule: 1.3.0 tinyglobby: 0.2.17 - unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.10)(vite@8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(vite@8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1)) unplugin-utils: 0.3.2 vue: 3.5.43(typescript@6.0.3) optionalDependencies: '@vue/compiler-sfc': 3.5.43 - vite: 8.3.1(@types/node@24.19.0)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) + vite: 8.3.2(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.6.1)(tsx@4.23.15)(yaml@2.9.1) transitivePeerDependencies: - '@farmfe/core' - '@rsbuild/core' @@ -8573,10 +8570,10 @@ snapshots: dependencies: vue: 3.5.43(typescript@6.0.3) - vue-tsc@3.3.11(typescript@6.0.3): + vue-tsc@3.3.12(typescript@6.0.3): dependencies: '@volar/typescript': 2.4.28(typescript@6.0.3) - '@vue/language-core': 3.3.11 + '@vue/language-core': 3.3.12 typescript: 6.0.3 vue@3.5.43(typescript@6.0.3): @@ -8728,7 +8725,7 @@ snapshots: cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - string-width: 8.2.2 + string-width: 8.3.0 y18n: 5.0.8 yargs-parser: 22.0.0 diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 92b5fb66..a4333563 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -43,7 +43,7 @@ import { type HeartbeatRequest, type HeartbeatResponse, type IncomingRequest, - type IncomingRequestCommand, + IncomingRequestCommand, type JsonType, MapStringifyFormat, MessageType, @@ -52,6 +52,7 @@ import { OCPP20ReadingContextEnumType, OCPP20RequiredVariableName, type OCPP20TransactionEventRequest, + type OCPPCommandName, OCPPVersion, type OutgoingRequest, PowerUnits, @@ -98,6 +99,7 @@ import { ensureError, formatDurationMilliSeconds, formatDurationSeconds, + getEnumStringValue, getErrorMessage, getMessageTypeString, getWebSocketCloseEventStatusString, @@ -218,6 +220,12 @@ class TransactionEventQueuePersistenceDiscardedError extends BaseError {} class TransactionEventQueuePersistenceSupersededError extends BaseError {} export class ChargingStation extends EventEmitter { + private static readonly normalWebSocketCloseEventStatusCodes: ReadonlySet = + new Set([ + WebSocketCloseEventStatusCode.CLOSE_NO_STATUS, + WebSocketCloseEventStatusCode.CLOSE_NORMAL, + ]) + public automaticTransactionGenerator?: AutomaticTransactionGenerator public bootNotificationRequest?: BootNotificationRequest public bootNotificationResponse?: BootNotificationResponse @@ -1437,10 +1445,7 @@ export class ChargingStation extends EventEmitter { * @param command - OCPP command the statistic is recorded against. * @param messageType - Message type of the recorded exchange. */ - public recordRequestStatistic ( - command: IncomingRequestCommand | RequestCommand, - messageType: MessageType - ): void { + public recordRequestStatistic (command: OCPPCommandName, messageType: MessageType): void { if (this.stationInfo?.enableStatistics === true) { this.performanceStatistics?.addRequestStatistic(command, messageType) } @@ -3439,11 +3444,7 @@ export class ChargingStation extends EventEmitter { } } - private onClose ( - wsConnection: WebSocket, - code: WebSocketCloseEventStatusCode, - reason: Buffer - ): void { + private onClose (wsConnection: WebSocket, code: number, reason: Buffer): void { const closedByRequest = this.wsConnectionsClosedByRequest.delete(wsConnection) if (wsConnection !== this.wsConnection) { return @@ -3458,24 +3459,14 @@ export class ChargingStation extends EventEmitter { ) this.emitChargingStationEvent(ChargingStationEvents.disconnected) this.emitChargingStationEvent(ChargingStationEvents.updated) - switch (code) { - // Normal close - case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS: - case WebSocketCloseEventStatusCode.CLOSE_NORMAL: - logger.info( - `${this.logPrefix()} ${moduleName}.onClose: WebSocket normally closed with status '${getWebSocketCloseEventStatusString( - code - )}' and reason '${reason.toString()}'` - ) - break - // Abnormal close - default: - logger.error( - `${this.logPrefix()} ${moduleName}.onClose: WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString( - code - )}' and reason '${reason.toString()}'` - ) - break + if (ChargingStation.normalWebSocketCloseEventStatusCodes.has(code)) { + logger.info( + `${this.logPrefix()} ${moduleName}.onClose: WebSocket normally closed with status '${getWebSocketCloseEventStatusString(code)}' and reason '${reason.toString()}'` + ) + } else { + logger.error( + `${this.logPrefix()} ${moduleName}.onClose: WebSocket abnormally closed with status '${getWebSocketCloseEventStatusString(code)}' and reason '${reason.toString()}'` + ) } // Reconnect on any close we did not request while still started: a // server-initiated drop, or an internal close wanting a fresh connection @@ -3520,37 +3511,49 @@ export class ChargingStation extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-base-to-string request = JSON.parse(data.toString()) as ErrorResponse | IncomingRequest | Response if (Array.isArray(request)) { - ;[messageType] = request + const [parsedMessageType] = request if ( this.isStopping() && - messageType !== MessageType.CALL_RESULT_MESSAGE && - messageType !== MessageType.CALL_ERROR_MESSAGE + parsedMessageType !== MessageType.CALL_RESULT_MESSAGE && + parsedMessageType !== MessageType.CALL_ERROR_MESSAGE ) { return } - switch (messageType) { + switch (parsedMessageType) { // Error Message case MessageType.CALL_ERROR_MESSAGE: - this.handleErrorMessage(request as ErrorResponse) + messageType = MessageType.CALL_ERROR_MESSAGE + this.handleErrorMessage(request) break // Incoming Message - case MessageType.CALL_MESSAGE: + case MessageType.CALL_MESSAGE: { + messageType = MessageType.CALL_MESSAGE if (!messageLifecycleIsCurrent()) return + const commandName = getEnumStringValue(IncomingRequestCommand, request[2]) + if (commandName == null) { + throw new OCPPError( + ErrorType.NOT_IMPLEMENTED, + `Unknown OCPP command '${request[2]}'`, + OCPPConstants.UNKNOWN_OCPP_COMMAND, + { command: request[2] } + ) + } await this.handleIncomingMessage( - request as IncomingRequest, + [parsedMessageType, request[1], commandName, request[3]], messageLifecycleSignal, messageLifecycleIsCurrent, messageSourceIsCurrent ) break + } // Response Message case MessageType.CALL_RESULT_MESSAGE: - this.handleResponseMessage(request as Response) + messageType = MessageType.CALL_RESULT_MESSAGE + this.handleResponseMessage(request) break // Unknown Message default: - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - errorMsg = `Wrong message type ${messageType}` + errorMsg = `Wrong message type ${String(parsedMessageType)}` logger.error(`${this.logPrefix()} ${moduleName}.onMessage: ${errorMsg}`) throw new OCPPError( this.stationInfo?.ocppVersion !== OCPPVersion.VERSION_16 @@ -3603,8 +3606,8 @@ export class ChargingStation extends EventEmitter { } return } - let commandName: IncomingRequestCommand | undefined - let requestCommandName: IncomingRequestCommand | RequestCommand | undefined + let commandName: string | undefined + let requestCommandName: string | undefined let errorCallback: ErrorCallback const [, messageId] = request const ocppError = @@ -3625,11 +3628,13 @@ export class ChargingStation extends EventEmitter { this.requests.delete(messageId) } break - case MessageType.CALL_MESSAGE: + case MessageType.CALL_MESSAGE: { if (!messageLifecycleIsCurrent() || !messageSourceIsCurrent()) return - ;[, , commandName] = request as IncomingRequest - await this.ocppRequestService.sendError(this, messageId, ocppError, commandName) + const [, , rawCommandName] = request + commandName = typeof rawCommandName === 'string' ? rawCommandName : undefined + await this.ocppRequestService.sendError(this, messageId, ocppError, ocppError.command) break + } } if (!(error instanceof OCPPError)) { logger.warn( @@ -4322,8 +4327,8 @@ export class ChargingStation extends EventEmitter { onCompleteCallback() return } - if (isRequest) { - ;[, messageId, commandName] = parsedMessage as OutgoingRequest + if (parsedMessage[0] === MessageType.CALL_MESSAGE) { + ;[, messageId, commandName] = parsedMessage } const bufferedMessageInFlight: { entry: BufferedMessageEntry diff --git a/src/charging-station/HelpersConfig.ts b/src/charging-station/HelpersConfig.ts index cc43f9cc..31a68587 100644 --- a/src/charging-station/HelpersConfig.ts +++ b/src/charging-station/HelpersConfig.ts @@ -334,7 +334,7 @@ export const getDefaultVoltageOut = ( templateFile: string ): Voltage => { const errorMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out` - let defaultVoltageOut: number + let defaultVoltageOut: Voltage switch (currentType) { case CurrentType.AC: defaultVoltageOut = Voltage.VOLTAGE_230 diff --git a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts index 421c6dd8..a1dd1668 100644 --- a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts @@ -20,6 +20,8 @@ import { GetCertificateStatusEnumType, type HeartbeatResponse, Iso15118EVCertificateStatusEnumType, + isOCPP16MeterValue, + isOCPP20MeterValue, type MessageEvent, type MeterValuesRequest, type MeterValuesResponse, @@ -49,6 +51,7 @@ import { import { Constants, convertToInt, + getEnumStringValue, getErrorMessage, isAsyncFunction, isEmpty, @@ -116,17 +119,18 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne [ BroadcastChannelProcedureName.GET_15118_EV_CERTIFICATE, r => - (r as OCPP20Get15118EVCertificateResponse).status === + getEnumStringValue(Iso15118EVCertificateStatusEnumType, r.status) === Iso15118EVCertificateStatusEnumType.Accepted, ], [ BroadcastChannelProcedureName.GET_CERTIFICATE_STATUS, r => - (r as OCPP20GetCertificateStatusResponse).status === GetCertificateStatusEnumType.Accepted, + getEnumStringValue(GetCertificateStatusEnumType, r.status) === + GetCertificateStatusEnumType.Accepted, ], [ BroadcastChannelProcedureName.SIGN_CERTIFICATE, - r => (r as OCPP20SignCertificateResponse).status === GenericStatus.Accepted, + r => getEnumStringValue(GenericStatus, r.status) === GenericStatus.Accepted, ], ]) @@ -525,15 +529,21 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne ) } const requestedPublicKeyIncluded = - requestedMeterValues?.some(meterValue => - isOcpp2 - ? (meterValue as OCPP20MeterValue).sampledValue.some(sampledValue => - isNotEmptyString(getRawSignedMeterValuePublicKey(sampledValue)) - ) - : (meterValue as OCPP16MeterValue).sampledValue.some(sampledValue => - isNotEmptyString(getOCPP16SignedMeterValuePublicKey(sampledValue)) - ) - ) === true + requestedMeterValues?.some(meterValue => { + if (isOcpp2 && isOCPP20MeterValue(meterValue)) { + return meterValue.sampledValue.some(sampledValue => + isNotEmptyString(getRawSignedMeterValuePublicKey(sampledValue)) + ) + } + if (!isOcpp2 && isOCPP16MeterValue(meterValue)) { + return meterValue.sampledValue.some(sampledValue => + isNotEmptyString(getOCPP16SignedMeterValuePublicKey(sampledValue)) + ) + } + throw new BaseError( + `${this.chargingStation.logPrefix()} ${moduleName}.handleMeterValues: meterValue does not match the station OCPP version` + ) + }) === true if (requestedMeterValues == null && connectorStatus?.transactionEnding === true) { throw new BaseError( `${this.chargingStation.logPrefix()} ${moduleName}.handleMeterValues: Transaction is ending` @@ -615,14 +625,20 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne // SignedData SampledValue when signing is enabled for the connector. // OCPP 2.0.x signing is applied inline by the versioned dispatcher. if (!isOcpp2 && transactionId != null) { + if (!isOCPP16MeterValue(meterValue)) { + throw new BaseError('Generated MeterValue does not match OCPP 1.6') + } publicKeyIncluded = OCPP16ServiceUtils.appendSignedUpdatedReadings( this.chargingStation, connectorId, convertToInt(transactionId), - meterValue as OCPP16MeterValue + meterValue ) } else if (isOcpp2) { - publicKeyIncluded = (meterValue as OCPP20MeterValue).sampledValue.some(sampledValue => + if (!isOCPP20MeterValue(meterValue)) { + throw new BaseError('Generated MeterValue does not match OCPP 2.0.x') + } + publicKeyIncluded = meterValue.sampledValue.some(sampledValue => isNotEmptyString(getRawSignedMeterValuePublicKey(sampledValue)) ) } diff --git a/src/charging-station/meter-values/CoherentMeterValueBuilder.ts b/src/charging-station/meter-values/CoherentMeterValueBuilder.ts index efcb3305..330bc0a0 100644 --- a/src/charging-station/meter-values/CoherentMeterValueBuilder.ts +++ b/src/charging-station/meter-values/CoherentMeterValueBuilder.ts @@ -34,6 +34,8 @@ import { MeterValuePhase, MeterValueUnit, OCPP16MeterValueFormat, + OCPP16MeterValueMeasurand, + OCPP20MeasurandEnumType, OCPPVersion, } from '../../types/index.js' import { @@ -185,11 +187,17 @@ const energyIntervalFallbackIdentity = ( JSON.stringify([ template.phase, template.location ?? MeterValueLocation.OUTLET, - (template.unit as MeterValueUnit | undefined) ?? MeterValueUnit.WATT_HOUR, + ocppVersion === OCPPVersion.VERSION_16 + ? (template.unit ?? MeterValueUnit.WATT_HOUR) + : (template.unitOfMeasure?.unit ?? template.unit ?? MeterValueUnit.WATT_HOUR), + ocppVersion === OCPPVersion.VERSION_16 ? 0 : (template.unitOfMeasure?.multiplier ?? 0), context ?? template.context ?? MeterValueContext.SAMPLE_PERIODIC, ocppVersion === OCPPVersion.VERSION_16 ? (template.format ?? OCPP16MeterValueFormat.RAW) - : canonicalizeCustomData(template.customData), + : [ + canonicalizeCustomData(template.customData), + canonicalizeCustomData(template.unitOfMeasure?.customData), + ], ]) const templateFamilyKey = ( @@ -201,7 +209,9 @@ const templateFamilyKey = ( customData: template.customData, location: template.location ?? MeterValueLocation.OUTLET, measurand: template.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, - unit: (template.unit as MeterValueUnit | undefined) ?? MeterValueUnit.WATT_HOUR, + unit: template.unitOfMeasure?.unit ?? template.unit ?? MeterValueUnit.WATT_HOUR, + unitCustomData: template.unitOfMeasure?.customData, + unitMultiplier: template.unitOfMeasure?.multiplier, }) /** @@ -492,6 +502,10 @@ const serializeCoherentMeterValue = ( const templates = resolveTemplates(context, connectorId, connectorStatus, evseIdOverride) const groups = groupTemplatesByMeasurand(templates) if (enabledMeasurands?.has(MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL) === true) { + const intervalMeasurand = + context.stationInfo?.ocppVersion === OCPPVersion.VERSION_16 + ? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL + : OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL const intervalTemplates = groups.get(MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL) ?? [] const intervalTemplateIdentities = new Set( intervalTemplates.map(template => @@ -502,8 +516,8 @@ const serializeCoherentMeterValue = ( .filter(template => { const intervalTemplate = { ...template, - measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, - } as SampledValueTemplate + measurand: intervalMeasurand, + } return !intervalTemplateIdentities.has( energyIntervalFallbackIdentity( intervalTemplate, @@ -512,13 +526,10 @@ const serializeCoherentMeterValue = ( ) ) }) - .map( - template => - ({ - ...template, - measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, - }) as SampledValueTemplate - ) + .map(template => ({ + ...template, + measurand: intervalMeasurand, + })) if (isNotEmptyArray(intervalFallbacks)) { groups.set(MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, [ ...intervalTemplates, @@ -580,10 +591,7 @@ const serializeCoherentMeterValue = ( measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL ? projectDcOutputValue(context, currentType, effectiveEvseId, template, raw) : raw - const unitDivider = resolveMeterValueUnitDivider( - measurand, - template.unit as MeterValueUnit | undefined - ) + const unitDivider = resolveMeterValueUnitDivider(measurand, template.unit) const scaled = measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL ? truncateTransactionIntervalValue(physicalValue / unitDivider) diff --git a/src/charging-station/meter-values/MeterValueUtils.ts b/src/charging-station/meter-values/MeterValueUtils.ts index 829bc033..bcf98a0c 100644 --- a/src/charging-station/meter-values/MeterValueUtils.ts +++ b/src/charging-station/meter-values/MeterValueUtils.ts @@ -97,7 +97,7 @@ export const areMeterValueUnitsCompatible = ( * Resolves the base-to-configured-unit divider for a meter-value measurand. * @param measurand - OCPP meter-value measurand. * @param unit - Configured output unit. - * @returns `Constants.UNIT_DIVIDER_KILO` for the family's kilo unit, otherwise `1`. + * @returns The named unit's divider from the family's base unit. */ export const resolveMeterValueUnitDivider = ( measurand: MeterValueMeasurand, @@ -107,6 +107,26 @@ export const resolveMeterValueUnitDivider = ( return kiloUnit != null && kiloUnit === unit ? Constants.UNIT_DIVIDER_KILO : 1 } +/** + * Resolves the scale from an emitted meter-value number to its physical base quantity. + * The named-unit scale and OCPP 2.0 decimal multiplier are cumulative. + * @param measurand - Meter-value measurand used to resolve base/kilo unit families. + * @param unit - Emitted unit name. + * @param multiplier - OCPP 2.0 decimal multiplier. + * @returns Scale from the emitted number to the physical base quantity. + */ +export const resolveMeterValueUnitScale = ( + measurand: MeterValueMeasurand, + unit: string | undefined, + multiplier = 0 +): number => { + const namedUnitScale = + unit === MeterValueUnit.KILO_PASCAL + ? Constants.UNIT_DIVIDER_KILO + : resolveMeterValueUnitDivider(measurand, unit) + return namedUnitScale * 10 ** multiplier +} + /** * Resolves an OCPP line or line-to-neutral phase to its one-based line index. * @param phase - OCPP meter-value phase. @@ -137,6 +157,8 @@ export const resolveLinePhaseIndex = (phase: string | undefined): number | undef * @param identity.location - Effective measurement location * @param identity.measurand - Effective measurand * @param identity.unit - Effective unit + * @param identity.unitCustomData - Unit descriptor custom data + * @param identity.unitMultiplier - Effective decimal multiplier * @returns Stable serialized family identity */ export const buildSampledValueFamilyKey = (identity: { @@ -144,12 +166,16 @@ export const buildSampledValueFamilyKey = (identity: { customData: unknown location: MeterValueLocation | undefined measurand: MeterValueMeasurand - unit: MeterValueUnit | undefined + unit: string | undefined + unitCustomData?: unknown + unitMultiplier?: number }): string => JSON.stringify([ identity.context, identity.location, identity.unit, + identity.unitMultiplier ?? 0, identity.measurand, canonicalizeCustomData(identity.customData), + canonicalizeCustomData(identity.unitCustomData), ]) diff --git a/src/charging-station/meter-values/TransactionIntervalUtils.ts b/src/charging-station/meter-values/TransactionIntervalUtils.ts index 1280b6fa..c434355e 100644 --- a/src/charging-station/meter-values/TransactionIntervalUtils.ts +++ b/src/charging-station/meter-values/TransactionIntervalUtils.ts @@ -1,5 +1,6 @@ import { type ConnectorStatus, CurrentType, MeterValueMeasurand } from '../../types/index.js' -import { Constants, isEmpty } from '../../utils/index.js' +import { isEmpty } from '../../utils/index.js' +import { resolveMeterValueUnitScale } from './MeterValueUtils.js' export interface TransactionIntervalState { consumed: number @@ -112,15 +113,17 @@ export const getRepresentedTransactionIntervalEnergyWh = ( : Number.parseFloat(sampledValue.value) if (!Number.isFinite(value)) continue const unit = sampledValue.unitOfMeasure?.unit ?? sampledValue.unit - const unitMultiplier = - unit === 'kWh' ? Constants.UNIT_DIVIDER_KILO : unit === 'MWh' ? 1_000_000 : 1 - const decimalMultiplier = 10 ** (sampledValue.unitOfMeasure?.multiplier ?? 0) + const unitScale = resolveMeterValueUnitScale( + MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + unit, + sampledValue.unitOfMeasure?.multiplier + ) const phaseMultiplier = /^L[123](?:-N)?$/.test(sampledValue.phase ?? '') ? numberOfPhases : 1 const locationMultiplier = sampledValue.location === 'Inlet' && inletToOutputEfficiency > 0 ? inletToOutputEfficiency : 1 representedEnergyWh = Math.max( representedEnergyWh, - value * unitMultiplier * decimalMultiplier * phaseMultiplier * locationMultiplier + value * unitScale * phaseMultiplier * locationMultiplier ) } return representedEnergyWh diff --git a/src/charging-station/meter-values/index.ts b/src/charging-station/meter-values/index.ts index 45904dd5..9b6d6c58 100644 --- a/src/charging-station/meter-values/index.ts +++ b/src/charging-station/meter-values/index.ts @@ -58,6 +58,7 @@ export { type MeterValueUnitFamily, resolveLinePhaseIndex, resolveMeterValueUnitDivider, + resolveMeterValueUnitScale, } from './MeterValueUtils.js' export { captureTransactionIntervalState, diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 99111484..53e4d330 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -36,7 +36,7 @@ import { type TransactionMeterValueDelivery, TransactionMeterValueDeliveryBarrier, } from '../../../charging-station/meter-values/index.js' -import { OCPPError } from '../../../exception/index.js' +import { BaseError, OCPPError } from '../../../exception/index.js' import { type ChangeConfigurationRequest, type ChangeConfigurationResponse, @@ -53,6 +53,7 @@ import { type GetDiagnosticsResponse, type IncomingRequestCommand, type IncomingRequestHandler, + isOCPP16ChargingProfile, type JsonType, type LogConfiguration, OCPP16AuthorizationStatus, @@ -83,7 +84,6 @@ import { type OCPP16HeartbeatResponse, OCPP16IncomingRequestCommand, OCPP16MessageTrigger, - type OCPP16MeterValue, OCPP16MeterValueContext, type OCPP16MeterValuesRequest, type OCPP16MeterValuesResponse, @@ -108,6 +108,7 @@ import { type RemoteStopTransactionRequest, ReservationTerminationReason, type ResetRequest, + ResetType, type SetChargingProfileRequest, type SetChargingProfileResponse, type StatusNotificationOptions, @@ -123,6 +124,7 @@ import { convertToIntOrNaN, ensureError, formatDurationMilliSeconds, + getEnumStringValue, handleIncomingRequestError, isEmpty, isNotEmptyArray, @@ -138,6 +140,7 @@ import { OCPPConstants } from '../OCPPConstants.js' import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' import { isIdTagAuthorized } from '../OCPPServiceOperations.js' import { + assertMeterValueBuilderResult, buildMeterValue, createPayloadValidatorMap, isConnectorIdValid, @@ -154,6 +157,38 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' const moduleName = 'OCPP16IncomingRequestService' +const requireOCPP16ChargePointStatus = (status: string | undefined): OCPP16ChargePointStatus => { + const ocpp16Status = getEnumStringValue(OCPP16ChargePointStatus, status) + if (ocpp16Status == null) { + throw new BaseError(`Invalid OCPP 1.6 charge point status '${String(status)}'`) + } + return ocpp16Status +} + +const resolveTriggeredStatusNotificationStatus = ( + chargingStation: ChargingStation, + connectorId: number, + status: string | undefined +): OCPP16ChargePointStatus => + requireOCPP16ChargePointStatus( + status ?? + (connectorId === 0 + ? chargingStation.getConnectorStatus(0)?.availability === OCPP16AvailabilityType.Operative + ? OCPP16ChargePointStatus.Available + : OCPP16ChargePointStatus.Unavailable + : undefined) + ) + +const requireOCPP16ChargingProfiles = ( + chargingProfiles: ConnectorStatus['chargingProfiles'] +): OCPP16ChargingProfile[] => { + const profiles = chargingProfiles ?? [] + if (!profiles.every(isOCPP16ChargingProfile)) { + throw new BaseError('Charging profiles do not match OCPP 1.6') + } + return profiles +} + /** * Per-station lifecycle state carried on {@link OCPP16IncomingRequestService}. * @@ -752,8 +787,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService 0 && valueChanged ) { @@ -1247,7 +1277,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (chargingStation.lifecycleAbortSignal !== lifecycleSignal || lifecycleSignal.aborted) { @@ -1898,7 +1936,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService( + enumObject: Readonly>, + value: string | undefined, + fieldName: string, + providedValue: string | undefined +): T | undefined => { + const resolvedValue = getEnumStringValue(enumObject, value) + if (providedValue != null && resolvedValue == null) { + throw new BaseError(`Invalid OCPP 1.6 sampled value ${fieldName} '${providedValue}'`) + } + return resolvedValue +} + export const buildOCPP16BootNotificationRequest = ( stationInfo: ChargingStationInfo ): OCPP16BootNotificationRequest => ({ @@ -51,15 +68,48 @@ export function buildOCPP16SampledValue ( context?: MeterValueContext, phase?: MeterValuePhase ): OCPP16SampledValue { + if (sampledValueTemplate.unitOfMeasure != null) { + throw new BaseError('Invalid OCPP 1.6 sampled value unitOfMeasure') + } const fields = resolveSampledValueFields(sampledValueTemplate, value, context, phase) + const resolvedContext = resolveOCPP16EnumValue( + OCPP16MeterValueContext, + fields.context, + 'context', + context ?? sampledValueTemplate.context + ) + const resolvedLocation = resolveOCPP16EnumValue( + OCPP16MeterValueLocation, + fields.location, + 'location', + sampledValueTemplate.location + ) + const resolvedMeasurand = resolveOCPP16EnumValue( + OCPP16MeterValueMeasurand, + fields.measurand, + 'measurand', + sampledValueTemplate.measurand + ) + const resolvedPhase = resolveOCPP16EnumValue( + OCPP16MeterValuePhase, + fields.phase, + 'phase', + phase ?? sampledValueTemplate.phase + ) + const resolvedUnit = resolveOCPP16EnumValue( + OCPP16MeterValueUnit, + fields.unit, + 'unit', + sampledValueTemplate.unit + ) return { - context: fields.context, - location: fields.location, - measurand: fields.measurand, - ...(fields.unit != null && { unit: fields.unit }), + ...(resolvedContext != null && { context: resolvedContext }), + ...(resolvedLocation != null && { location: resolvedLocation }), + ...(resolvedMeasurand != null && { measurand: resolvedMeasurand }), + ...(resolvedUnit != null && { unit: resolvedUnit }), value: fields.value.toString(), - ...(fields.phase != null && { phase: fields.phase }), - } as OCPP16SampledValue + ...(resolvedPhase != null && { phase: resolvedPhase }), + } } /** diff --git a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts index fc5a49f8..5f7025ed 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts @@ -8,8 +8,6 @@ import { isWithinInterval, } from 'date-fns' -import type { SigningMethodEnumType } from '../../../types/index.js' - import { type ChargingStation, getConfigurationKey, @@ -32,6 +30,7 @@ import { type ConnectorStatus, ErrorType, type GenericResponse, + type MeterValue, type MeterValuesRequest, type MeterValuesResponse, OCPP16AuthorizationStatus, @@ -75,6 +74,7 @@ import { convertToDate, convertToInt, ensureError, + getEnumStringValue, isNotEmptyArray, isNotEmptyString, logger, @@ -84,6 +84,7 @@ import { import { mapOCPP16Status, OCPPAuthServiceFactory } from '../auth/index.js' import { sendAndSetConnectorStatus } from '../OCPPConnectorStatusOperations.js' import { + assertMeterValueBuilderResult, buildEmptyMeterValue, buildMeterValue, createPayloadConfigs, @@ -108,6 +109,12 @@ import { OCPP16Constants } from './OCPP16Constants.js' import { buildOCPP16SampledValue, buildSignedOCPP16SampledValue } from './OCPP16RequestBuilders.js' const moduleName = 'OCPP16ServiceUtils' + +const requireOCPP16MeterValue = (meterValue: MeterValue): OCPP16MeterValue => { + assertMeterValueBuilderResult(meterValue, OCPPVersion.VERSION_16) + return meterValue +} + const RFC3339_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?([Zz]|([+-])(\d{2}):(\d{2}))$/ const DAYS_PER_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as const @@ -352,7 +359,7 @@ export class OCPP16ServiceUtils { transactionId ) } - const meterValue = buildEmptyMeterValue() as OCPP16MeterValue + const meterValue = requireOCPP16MeterValue(buildEmptyMeterValue()) const meterStartOutputWh = meterStart ?? 0 // Energy.Active.Import.Register measurand (default) const sampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId) @@ -456,7 +463,7 @@ export class OCPP16ServiceUtils { sampledValueTemplate.unit === OCPP16MeterValueUnit.KILO_WATT_HOUR ? Constants.UNIT_DIVIDER_KILO : 1 - const meterValue = { sampledValue: [], timestamp } as OCPP16MeterValue + const meterValue = requireOCPP16MeterValue(buildEmptyMeterValue(timestamp)) meterValue.sampledValue.push( buildOCPP16SampledValue( sampledValueTemplate, @@ -1137,11 +1144,9 @@ export class OCPP16ServiceUtils { const sampleAt = Date.now() const elapsedInterval = Math.max(elapsedIntervalCarry, sampleAt - lastSampleAt) const intervalState = captureTransactionIntervalState(connectorStatus) - const meterValue = buildMeterValue( - chargingStation, - transactionId, - elapsedInterval - ) as OCPP16MeterValue + const meterValue = requireOCPP16MeterValue( + buildMeterValue(chargingStation, transactionId, elapsedInterval) + ) lastSampleAt = sampleAt elapsedIntervalCarry = 0 completeTransactionIntervalState(intervalState, 'default', [meterValue]) @@ -1331,6 +1336,18 @@ export class OCPP16ServiceUtils { ) } + const rawStopReason = reason ?? requestOverrides.reason + const validatedStopReason = getEnumStringValue(OCPP16StopTransactionReason, rawStopReason) + if (rawStopReason !== undefined && validatedStopReason == null) { + return Promise.reject( + new OCPPError( + ErrorType.FORMAT_VIOLATION, + `${moduleName}.stopTransactionOnConnector: Invalid StopTransaction reason '${rawStopReason}'`, + RequestCommand.STOP_TRANSACTION + ) + ) + } + let transactionEndingOwned = true const periodicMeterValuesWasRunning = connectorStatus.transactionUpdatedMeterValuesSetInterval != null @@ -1506,7 +1523,7 @@ export class OCPP16ServiceUtils { timestamp, transactionData, transactionId, - ...(reason != null && { reason: reason as StopTransactionRequest['reason'] }), + ...(validatedStopReason != null && { reason: validatedStopReason }), }) if ( strictEndMeterValueIsSolePublicKeyCarrier && @@ -1998,13 +2015,15 @@ export class OCPP16ServiceUtils { getConfigurationKey(chargingStation, startTxnSampledDataKey)?.value != null ? startTxnSampledDataKey : undefined - return buildMeterValue( - chargingStation, - transactionId, - 0, - measurandsKey, - OCPP16MeterValueContext.TRANSACTION_BEGIN - ) as OCPP16MeterValue + return requireOCPP16MeterValue( + buildMeterValue( + chargingStation, + transactionId, + 0, + measurandsKey, + OCPP16MeterValueContext.TRANSACTION_BEGIN + ) + ) } private static buildSignedSampledValue ( @@ -2058,23 +2077,25 @@ export class OCPP16ServiceUtils { chargingStation.getConnectorStatus(connectorId)?.transactionEnergyActiveImportIntervalCarry ?.default ?? 0 if (intervalEnergyWh <= 0) return - const intervalMeterValue = buildMeterValue( - chargingStation, - transactionId, - 0, - OCPP16StandardParametersKey.StopTxnSampledData, - OCPP16MeterValueContext.TRANSACTION_END, - false, - { - advanceEnergy: false, - connectorId, - energyIntervalWhOverride: intervalEnergyWh, - energyRegisterWhOverride: meterStop, - projectDcForLocation: true, - snapshot: true, - timestamp: meterValue.timestamp, - } - ) as OCPP16MeterValue + const intervalMeterValue = requireOCPP16MeterValue( + buildMeterValue( + chargingStation, + transactionId, + 0, + OCPP16StandardParametersKey.StopTxnSampledData, + OCPP16MeterValueContext.TRANSACTION_END, + false, + { + advanceEnergy: false, + connectorId, + energyIntervalWhOverride: intervalEnergyWh, + energyRegisterWhOverride: meterStop, + projectDcForLocation: true, + snapshot: true, + timestamp: meterValue.timestamp, + } + ) + ) const intervalSamples = intervalMeterValue.sampledValue.filter( sampledValue => sampledValue.measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL @@ -2237,7 +2258,7 @@ export class OCPP16ServiceUtils { const configuredSigningMethod = getConfigurationKey( chargingStation, OCPP16VendorParametersKey.SigningMethod - )?.value as SigningMethodEnumType | undefined + )?.value const prerequisiteResult = validateSigningPrerequisites(publicKeyHex, configuredSigningMethod) if (!prerequisiteResult.enabled) { diff --git a/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts b/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts index 4b0e3310..94f141f5 100644 --- a/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts +++ b/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts @@ -20,6 +20,7 @@ import { import { atomicWriteFile, convertToDate, + getEnumStringValue, getErrorMessage, isEmpty, isNotEmptyArray, @@ -288,10 +289,14 @@ export class OCPP20CertificateManager { .map(dirent => dirent.name) for (const certType of certTypes) { - if (filterTypes != null && isNotEmptyArray(filterTypes)) { - if (!filterTypes.includes(certType as InstallCertificateUseEnumType)) { - continue - } + const certificateType = getEnumStringValue(InstallCertificateUseEnumType, certType) + if (certificateType == null) continue + if ( + filterTypes != null && + isNotEmptyArray(filterTypes) && + !filterTypes.includes(certificateType) + ) { + continue } const certTypeDir = join(basePath, certType) @@ -307,9 +312,7 @@ export class OCPP20CertificateManager { certificateHashDataChain.push({ certificateHashData: hashData, - certificateType: this.mapInstallTypeToGetType( - certType as InstallCertificateUseEnumType - ), + certificateType: this.mapInstallTypeToGetType(certificateType), }) } catch { // Skip unreadable or unparsable certificate file diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts index b6fde07f..b5c77d42 100644 --- a/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts +++ b/src/charging-station/ocpp/2.0/OCPP20RequestBuilders.ts @@ -1,18 +1,29 @@ import type { StopTransactionReason } from '../../../types/index.js' +import { BaseError } from '../../../exception/index.js' import { BootReasonEnumType, type ChargingStationInfo, type MeterValueContext, type MeterValuePhase, + MeterValueUnit, OCPP16StopTransactionReason, type OCPP20BootNotificationRequest, + OCPP20LocationEnumType, OCPP20MeasurandEnumType, + OCPP20PhaseEnumType, + OCPP20ReadingContextEnumType, OCPP20ReasonEnumType, type OCPP20SampledValue, OCPP20TriggerReasonEnumType, type SampledValueTemplate, } from '../../../types/index.js' +import { convertToFloat, getEnumStringValue } from '../../../utils/index.js' +import { + areMeterValueUnitsCompatible, + getMeterValueUnitFamily, + resolveMeterValueUnitScale, +} from '../../meter-values/index.js' import { resolveSampledValueFields } from '../OCPPServiceUtils.js' import { generateSignedMeterData, @@ -24,6 +35,19 @@ import { type SignedSampledValueResult, } from '../OCPPSignedMeterValueUtils.js' +const requireOCPP20EnumValue = ( + enumObject: Readonly>, + value: string | undefined, + fieldName: string +): T | undefined => { + if (value == null) return undefined + const resolvedValue = getEnumStringValue(enumObject, value) + if (resolvedValue == null) { + throw new BaseError(`Invalid OCPP 2.0.x sampled value ${fieldName} '${value}'`) + } + return resolvedValue +} + export const buildOCPP20BootNotificationRequest = ( stationInfo: ChargingStationInfo, bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp @@ -50,7 +74,7 @@ export const buildOCPP20BootNotificationRequest = ( /** * Builds an OCPP 2.0.1 sampled value from a template and measurement data. * @param sampledValueTemplate - The sampled value template to use. - * @param value - The measured value. + * @param value - The measured value expressed in the template's flat/default unit. * @param context - The reading context. * @param phase - The phase of the measurement. * @param signingConfig - Optional signing configuration for generating signedMeterValue. @@ -64,17 +88,61 @@ export function buildOCPP20SampledValue ( signingConfig?: SampledValueSigningConfig ): SignedSampledValueResult { const fields = resolveSampledValueFields(sampledValueTemplate, value, context, phase) + const resolvedContext = requireOCPP20EnumValue( + OCPP20ReadingContextEnumType, + fields.context, + 'context' + ) + const resolvedLocation = requireOCPP20EnumValue( + OCPP20LocationEnumType, + fields.location, + 'location' + ) + const resolvedMeasurand = requireOCPP20EnumValue( + OCPP20MeasurandEnumType, + fields.measurand, + 'measurand' + ) + const resolvedPhase = requireOCPP20EnumValue(OCPP20PhaseEnumType, fields.phase, 'phase') + const sourceUnit = fields.unit + const emittedUnit = sampledValueTemplate.unitOfMeasure?.unit ?? sourceUnit + if ( + sourceUnit != null && + emittedUnit != null && + sourceUnit !== emittedUnit && + !areMeterValueUnitsCompatible(fields.measurand, sourceUnit, emittedUnit) + ) { + throw new BaseError( + `Cannot convert OCPP 2.0.x sampled value unit '${sourceUnit}' to '${emittedUnit}'` + ) + } + const unitOfMeasure = + sampledValueTemplate.unitOfMeasure != null || sourceUnit != null + ? { + ...sampledValueTemplate.unitOfMeasure, + ...(sampledValueTemplate.unitOfMeasure?.unit == null && + sourceUnit != null && { unit: sourceUnit }), + } + : undefined + const emittedScale = resolveMeterValueUnitScale( + fields.measurand, + emittedUnit, + unitOfMeasure?.multiplier + ) + const emittedValue = + (convertToFloat(fields.value) * resolveMeterValueUnitScale(fields.measurand, sourceUnit)) / + emittedScale const sampledValue: OCPP20SampledValue = { ...(sampledValueTemplate.customData != null && { customData: sampledValueTemplate.customData, }), - context: fields.context, - location: fields.location, - measurand: fields.measurand, - ...(fields.unit != null && { unitOfMeasure: { unit: fields.unit } }), - value: fields.value, - ...(fields.phase != null && { phase: fields.phase }), - } as OCPP20SampledValue + ...(resolvedContext != null && { context: resolvedContext }), + ...(resolvedLocation != null && { location: resolvedLocation }), + ...(resolvedMeasurand != null && { measurand: resolvedMeasurand }), + ...(unitOfMeasure != null && { unitOfMeasure }), + value: emittedValue, + ...(resolvedPhase != null && { phase: resolvedPhase }), + } let publicKeyIncluded = false @@ -86,11 +154,12 @@ export function buildOCPP20SampledValue ( signingConfig.publicKeyWithSignedMeterValue, signingConfig.publicKeySentInTransaction ) + const unitFamily = getMeterValueUnitFamily(fields.measurand, emittedUnit) const signedMeterDataParams: SignedMeterDataParams = { context: fields.context, meterSerialNumber: signingConfig.meterSerialNumber, - meterValue: fields.value, - meterValueUnit: fields.unit, + meterValue: emittedValue * emittedScale, + meterValueUnit: getEnumStringValue(MeterValueUnit, unitFamily?.baseUnit ?? emittedUnit), timestamp: signingConfig.timestamp ?? new Date(), transactionId: signingConfig.transactionId, } diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index e2bdb63d..08c1afbb 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -38,6 +38,7 @@ import { recordPendingSharedEnergy, resolveInletToOutputEfficiency, resolveLinePhaseIndex, + resolveMeterValueUnitScale, resolveRootSeed, type TransactionMeterValueDelivery, TransactionMeterValueDeliveryBarrier, @@ -52,6 +53,7 @@ import { type ConnectorStatusEnum, CurrentType, ErrorType, + isOCPP20MeterValue, type MeterValue, MeterValueLocation, OCPP20AuthorizationStatusEnumType, @@ -124,6 +126,7 @@ import { } from '../auth/index.js' import { sendPostTransactionStatus } from '../OCPPConnectorStatusOperations.js' import { + assertMeterValueBuilderResult, buildClockAlignedConnectorMeterValue, buildMeterValue, createPayloadConfigs, @@ -740,9 +743,11 @@ const normalizeClockAlignedAdditiveSample = ( unitFamily: MeterValueUnitFamily ): OCPP20SampledValue => { const configuredUnit = sampledValue.unitOfMeasure?.unit - const namedUnitMultiplier = - configuredUnit === unitFamily.kiloUnit ? Constants.UNIT_DIVIDER_KILO : 1 - const decimalMultiplier = 10 ** (sampledValue.unitOfMeasure?.multiplier ?? 0) + const unitScale = resolveMeterValueUnitScale( + sampledValue.measurand ?? OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, + configuredUnit, + sampledValue.unitOfMeasure?.multiplier + ) return { ...sampledValue, unitOfMeasure: { @@ -750,7 +755,7 @@ const normalizeClockAlignedAdditiveSample = ( multiplier: 0, unit: unitFamily.baseUnit, }, - value: sampledValue.value * namedUnitMultiplier * decimalMultiplier, + value: sampledValue.value * unitScale, } } @@ -1545,7 +1550,8 @@ export class OCPP20ServiceUtils { energyRegisterWhOverride: sharedEnergyRegisterWh, }), } - ) as OCPP20MeterValue + ) + assertMeterValueBuilderResult(meterValue, OCPPVersion.VERSION_201) const sharedObservationEnergyWh = usesSharedEvseRegister && context !== OCPP20ReadingContextEnumType.TRANSACTION_BEGIN && @@ -4615,10 +4621,14 @@ export class OCPP20ServiceUtils { evseId?: number ): OCPP20MeterValue[] { const connectorStatus = chargingStation.getConnectorStatus(connectorId, evseId) - const endedMeterValues = (connectorStatus?.transactionEndedMeterValues ?? - []) as OCPP20MeterValue[] - const beginMeterValue = connectorStatus?.transactionBeginMeterValue as - OCPP20MeterValue | undefined + const endedMeterValues = connectorStatus?.transactionEndedMeterValues ?? [] + if (!endedMeterValues.every(isOCPP20MeterValue)) { + throw new BaseError('Stored transaction-ended MeterValues do not match OCPP 2.0.x') + } + const beginMeterValue = connectorStatus?.transactionBeginMeterValue + if (beginMeterValue != null && !isOCPP20MeterValue(beginMeterValue)) { + throw new BaseError('Stored transaction-begin MeterValue does not match OCPP 2.0.x') + } const measurandsKey = buildConfigKey( OCPP20ComponentName.SampledDataCtrlr, OCPP20RequiredVariableName.TxEndedMeasurands diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts index f9d2a85a..012da23b 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts @@ -17,7 +17,13 @@ import { SetVariableStatusEnumType, type VariableType, } from '../../../types/index.js' -import { Constants, convertToIntOrNaN, isEmpty, logger } from '../../../utils/index.js' +import { + Constants, + convertToIntOrNaN, + getEnumStringValue, + isEmpty, + logger, +} from '../../../utils/index.js' import { addConfigurationKey, buildConfigKey, @@ -42,10 +48,10 @@ import { } from './OCPP20VariableRegistry.js' const isOCPP20ComponentName = (name: string): name is OCPP20ComponentName => { - return Object.values(OCPP20ComponentName).includes(name as OCPP20ComponentName) + return getEnumStringValue(OCPP20ComponentName, name) != null } const isOCPP20RequiredVariableName = (name: string): name is OCPP20RequiredVariableName => { - return Object.values(OCPP20RequiredVariableName).includes(name as OCPP20RequiredVariableName) + return getEnumStringValue(OCPP20RequiredVariableName, name) != null } const computeConfigurationKeyName = (variableMetadata: VariableMetadata): string => diff --git a/src/charging-station/ocpp/OCPPConstants.ts b/src/charging-station/ocpp/OCPPConstants.ts index 8d8568e0..4e5c8d46 100644 --- a/src/charging-station/ocpp/OCPPConstants.ts +++ b/src/charging-station/ocpp/OCPPConstants.ts @@ -5,11 +5,11 @@ import { ConfigurationStatus, DataTransferStatus, GenericStatus, - type IncomingRequestCommand, MeterValueMeasurand, - type RequestCommand, + type OCPPCommandName, ReservationStatus, TriggerMessageStatus, + UNKNOWN_OCPP_COMMAND, UnlockStatus, } from '../../types/index.js' import { Constants } from '../../utils/index.js' @@ -161,8 +161,7 @@ export class OCPPConstants { }) static readonly OCPP_WEBSOCKET_TIMEOUT_MS = 60_000 - static readonly UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' as - IncomingRequestCommand | RequestCommand + static readonly UNKNOWN_OCPP_COMMAND: OCPPCommandName = UNKNOWN_OCPP_COMMAND protected constructor () { // This is intentional diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index 5a098b47..923bffc2 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -10,9 +10,10 @@ import { type ErrorCallback, type ErrorResponse, ErrorType, - type IncomingRequestCommand, + IncomingRequestCommand, type JsonType, MessageType, + type OCPPCommandName, type OCPPVersion, type OutgoingRequest, type PendingRequestCancellationCallback, @@ -28,6 +29,7 @@ import { ensureError, formatDurationMilliSeconds, generateUUID, + getEnumStringValue, getErrorMessage, getMessageTypeString, handleSendMessageError, @@ -42,6 +44,17 @@ import { validatePayload, } from './OCPPServiceUtils.js' +const requireOCPPCommand = ( + commands: Readonly>, + commandName: string +): T => { + const command = getEnumStringValue(commands, commandName) + if (command == null) { + throw new OCPPError(ErrorType.NOT_SUPPORTED, `Unknown OCPP command '${commandName}'`) + } + return command +} + interface OutgoingCallCancellationState { readonly destructiveError: OCPPError | undefined readonly destructiveGeneration: number @@ -410,7 +423,7 @@ export abstract class OCPPRequestService { chargingStation: ChargingStation, messageId: string, ocppError: OCPPError, - commandName: IncomingRequestCommand | RequestCommand + commandName: OCPPCommandName ): Promise { try { return await this.internalSendMessage( @@ -477,11 +490,12 @@ export abstract class OCPPRequestService { payload: T, { forceValidation = false }: { forceValidation?: boolean } = {} ): boolean { + const requestCommand = getEnumStringValue(RequestCommand, commandName) return validatePayload( chargingStation, commandName, payload, - this.payloadValidatorFunctions.get(commandName as RequestCommand), + requestCommand == null ? undefined : this.payloadValidatorFunctions.get(requestCommand), 'request', true, forceValidation @@ -565,13 +579,16 @@ export abstract class OCPPRequestService { commandName: IncomingRequestCommand | RequestCommand, payload: T ): boolean { + const incomingRequestCommand = getEnumStringValue(IncomingRequestCommand, commandName) return validatePayload( chargingStation, commandName, payload, - this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get( - commandName as IncomingRequestCommand - ), + incomingRequestCommand == null + ? undefined + : this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get( + incomingRequestCommand + ), 'incoming request response', true ) @@ -582,7 +599,7 @@ export abstract class OCPPRequestService { messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, - commandName: IncomingRequestCommand | RequestCommand + commandName: OCPPCommandName ): string { let messageToSend: string // Type of message @@ -606,20 +623,22 @@ export abstract class OCPPRequestService { break } // Request - case MessageType.CALL_MESSAGE: - this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType) + case MessageType.CALL_MESSAGE: { + const requestCommand = requireOCPPCommand(RequestCommand, commandName) + this.validateRequestPayload(chargingStation, requestCommand, messagePayload as JsonType) messageToSend = JSON.stringify([ messageType, messageId, - commandName as RequestCommand, + requestCommand, messagePayload as JsonType, ] satisfies OutgoingRequest) break + } // Response case MessageType.CALL_RESULT_MESSAGE: this.validateIncomingRequestResponsePayload( chargingStation, - commandName, + requireOCPPCommand(IncomingRequestCommand, commandName), messagePayload as JsonType ) messageToSend = JSON.stringify([ @@ -664,7 +683,7 @@ export abstract class OCPPRequestService { messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, - commandName: IncomingRequestCommand | RequestCommand, + commandName: OCPPCommandName, params: RequestParams, responseTimeoutMs: number ): PendingSendOperation { @@ -752,7 +771,12 @@ export abstract class OCPPRequestService { // reject an already-answered request while its handler is still running. chargingStation.requests.delete(messageId) this.ocppResponseService - .responseHandler(chargingStation, commandName as RequestCommand, payload, requestPayload) + .responseHandler( + chargingStation, + requireOCPPCommand(RequestCommand, commandName), + payload, + requestPayload + ) .then(() => { resolve(payload) return undefined @@ -911,7 +935,7 @@ export abstract class OCPPRequestService { chargingStation, messageId, messagePayload as JsonType, - commandName, + requireOCPPCommand(RequestCommand, commandName), responseCallback, errorCallback, cancelPendingSend, @@ -1046,7 +1070,7 @@ export abstract class OCPPRequestService { chargingStation, messageId, messagePayload as JsonType, - commandName, + requireOCPPCommand(RequestCommand, commandName), responseCallback, errorCallback, cancelPendingSend, @@ -1105,7 +1129,7 @@ export abstract class OCPPRequestService { chargingStation, messageId, messagePayload as JsonType, - commandName, + requireOCPPCommand(RequestCommand, commandName), responseCallback, errorCallback, cancelPendingSend, @@ -1203,7 +1227,7 @@ export abstract class OCPPRequestService { messageId: string, messagePayload: JsonType | OCPPError, messageType: MessageType, - commandName: IncomingRequestCommand | RequestCommand, + commandName: OCPPCommandName, params?: RequestParams, cancellationGenerationAtRequestStart?: number ): Promise { @@ -1219,7 +1243,7 @@ export abstract class OCPPRequestService { ((chargingStation.inUnknownState() || chargingStation.inPendingState() || chargingStation.inRejectedState()) && - commandName === RequestCommand.BOOT_NOTIFICATION) || + getEnumStringValue(RequestCommand, commandName) === RequestCommand.BOOT_NOTIFICATION) || (chargingStation.stationInfo?.ocppStrictCompliance === false && chargingStation.inUnknownState()) || chargingStation.inAcceptedState() || diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index b7e3977c..d20cdd3d 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -10,7 +10,6 @@ import type { BootReasonEnumType, OCPP20RequiredVariableName, OCPP20VendorVariableName, - SigningMethodEnumType, } from '../../types/index.js' import { @@ -31,6 +30,7 @@ import { resolveInletToOutputEfficiency, resolveLinePhaseIndex, resolveMeterValueUnitDivider, + resolveMeterValueUnitScale, resolveRootSeed, truncateTransactionIntervalValue, } from '../../charging-station/meter-values/index.js' @@ -41,9 +41,12 @@ import { type ConfigurationKeyType, type ConnectorStatus, CurrentType, + type CustomDataType, ErrorType, FileType, IncomingRequestCommand, + isOCPP16SampledValue, + isOCPP20SampledValue, type JsonType, type MeasurandPerPhaseSampledValueTemplates, type MeasurandValues, @@ -54,13 +57,19 @@ import { MeterValueMeasurand, MeterValuePhase, MeterValueUnit, + type OCPP16MeterValue, OCPP16MeterValueFormat, + type OCPP16MeterValueUnit, OCPP20ComponentName, + OCPP20LocationEnumType, + OCPP20MeasurandEnumType, type OCPP20MeterValue, OCPP20OptionalVariableName, OCPP20PhaseEnumType, OCPP20ReadingContextEnumType, type OCPP20SampledValue, + OCPP20UnitEnumType, + type OCPP20UnitOfMeasure, OCPPVersion, RequestCommand, type SampledValue, @@ -76,6 +85,7 @@ import { convertToFloat, convertToInt, DCElectricUtils, + getEnumStringValue, getRandomFloatFluctuatedRounded, getRandomFloatRounded, handleFileException, @@ -108,6 +118,63 @@ import { const moduleName = 'OCPPServiceUtils' +const LINE_PHASES = [MeterValuePhase.L1, MeterValuePhase.L2, MeterValuePhase.L3] as const +const LINE_TO_LINE_PHASES = [ + MeterValuePhase.L1_L2, + MeterValuePhase.L2_L3, + MeterValuePhase.L3_L1, +] as const +const LINE_TO_NEUTRAL_PHASES = [ + MeterValuePhase.L1_N, + MeterValuePhase.L2_N, + MeterValuePhase.L3_N, +] as const + +const toSnapshotBaselineSample = (sampledValue: SampledValue): OCPP20SampledValue => { + if (isOCPP20SampledValue(sampledValue)) return sampledValue + const context = getEnumStringValue(OCPP20ReadingContextEnumType, sampledValue.context) + const location = getEnumStringValue(OCPP20LocationEnumType, sampledValue.location) + const measurand = getEnumStringValue(OCPP20MeasurandEnumType, sampledValue.measurand) + const phase = getEnumStringValue(OCPP20PhaseEnumType, sampledValue.phase) + const unit = getEnumStringValue(OCPP20UnitEnumType, sampledValue.unit) + return { + ...(context != null && { context }), + ...(location != null && { location }), + ...(measurand != null && { measurand }), + ...(phase != null && { phase }), + ...(unit != null && { unitOfMeasure: { unit } }), + value: convertToFloat(sampledValue.value), + } +} + +export function assertMeterValueBuilderResult ( + meterValue: MeterValue, + version: OCPPVersion.VERSION_16 +): asserts meterValue is OCPP16MeterValue +export function assertMeterValueBuilderResult ( + meterValue: MeterValue, + version: OCPPVersion.VERSION_20 | OCPPVersion.VERSION_201 +): asserts meterValue is OCPP20MeterValue +/** + * Validates the protocol representation of an internal MeterValue builder result. + * Empty sampled-value arrays are valid omission signals inside builders. + * @param meterValue - Internal builder result to validate. + * @param version - OCPP version expected by the caller. + * @throws {BaseError} When a non-empty result contains a sample from another OCPP version. + */ +export function assertMeterValueBuilderResult (meterValue: MeterValue, version: OCPPVersion): void { + const matchesVersion = + meterValue.sampledValue.length === 0 || + (version === OCPPVersion.VERSION_16 + ? meterValue.sampledValue.every(isOCPP16SampledValue) + : meterValue.sampledValue.every(isOCPP20SampledValue)) + if (!matchesVersion) { + throw new BaseError( + `MeterValue builder returned sampled values that do not match OCPP ${version}` + ) + } +} + const isOCPP20FlagEnabled = ( chargingStation: ChargingStation, component: OCPP20ComponentName, @@ -1006,12 +1073,13 @@ const buildCurrentMeasurandValue = ( } /** - * Builds an empty MeterValue with no sampled values and the current timestamp. + * Builds an empty MeterValue with no sampled values. + * @param timestamp - Meter value timestamp; defaults to the current time * @returns Empty MeterValue object */ -export const buildEmptyMeterValue = (): MeterValue => ({ +export const buildEmptyMeterValue = (timestamp = new Date()): MeterValue => ({ sampledValue: [], - timestamp: new Date(), + timestamp, }) /** @@ -1162,7 +1230,7 @@ const createVersionedSampledValueDispatcher = ( const configuredSigningMethod = getConfigurationKey( chargingStation, buildConfigKey(OCPP20ComponentName.FiscalMetering, VendorParametersKey.SigningMethod) - )?.value as SigningMethodEnumType | undefined + )?.value const prerequisiteResult = validateSigningPrerequisites( publicKeyHex, @@ -1246,8 +1314,6 @@ const createVersionedSampledValueDispatcher = ( // abort semantics, no `resetStationState` hook — a diagnostic side-effect // cache freed only by GC. const warnedInvalidMeasurands = new WeakMap>() -const KNOWN_MEASURANDS: ReadonlySet = new Set(Object.values(MeterValueMeasurand)) - const getOrCreateWarnedMeasurands = (chargingStation: ChargingStation): Set => { let warned = warnedInvalidMeasurands.get(chargingStation) if (warned == null) { @@ -1302,8 +1368,9 @@ const resolveEnabledMeasurands = ( if (isEmpty(trimmed)) { continue } - if (KNOWN_MEASURANDS.has(trimmed)) { - enabled.add(trimmed as MeterValueMeasurand) + const measurand = getEnumStringValue(MeterValueMeasurand, trimmed) + if (measurand != null) { + enabled.add(measurand) continue } const warned = getOrCreateWarnedMeasurands(chargingStation) @@ -1338,11 +1405,17 @@ const getEnergyTemplateIdentity = ( return JSON.stringify([ resolved.phase, resolved.location, - resolved.unit, + ocppVersion === OCPPVersion.VERSION_16 + ? resolved.unit + : (template.unitOfMeasure?.unit ?? resolved.unit), + ocppVersion === OCPPVersion.VERSION_16 ? 0 : (template.unitOfMeasure?.multiplier ?? 0), resolved.context, ocppVersion === OCPPVersion.VERSION_16 ? (template.format ?? OCPP16MeterValueFormat.RAW) - : canonicalizeCustomData(template.customData), + : [ + canonicalizeCustomData(template.customData), + canonicalizeCustomData(template.unitOfMeasure?.customData), + ], ]) } @@ -1369,7 +1442,7 @@ const addEnergyIntervalTemplateFallbacks = ( { ...template, measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, - } as SampledValueTemplate, + }, ] }) } @@ -1492,7 +1565,7 @@ const buildEnergyIntervalSampledValues = ( physicalValue / resolveMeterValueUnitDivider( MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, - template.unit as string | undefined + template.unit ) ), context, @@ -1526,7 +1599,9 @@ const applySnapshotRegisterValuesWithoutPhases = ( customData: template.customData, location: identity.location, measurand: identity.measurand, - unit: identity.unit, + unit: template.unitOfMeasure?.unit ?? identity.unit, + unitCustomData: template.unitOfMeasure?.customData, + unitMultiplier: template.unitOfMeasure?.multiplier, }) const family = families.get(key) ?? [] family.push(template) @@ -1592,7 +1667,7 @@ const expandClockAlignedSnapshotSamples = ( ).map((template): SampledValueTemplate => { const measurand = template.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER return evseId === 0 && template.location == null && isElectricalMeasurand(measurand) - ? ({ ...template, location: MeterValueLocation.INLET } as SampledValueTemplate) + ? { ...template, location: MeterValueLocation.INLET } : template }) const templatesWithIntervalFallback = @@ -1608,14 +1683,18 @@ const expandClockAlignedSnapshotSamples = ( const templatesByIdentity = new Map() for (const template of templatesWithIntervalFallback) { const identity = resolveSampledValueFields(template, 0, context, template.phase) + const isOcpp16 = chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_16 const key = JSON.stringify([ - identity.measurand, identity.phase, - identity.location, - identity.unit, - chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_16 - ? template.format - : canonicalizeCustomData(template.customData), + buildSampledValueFamilyKey({ + context: identity.context, + customData: isOcpp16 ? template.format : template.customData, + location: identity.location, + measurand: identity.measurand, + unit: isOcpp16 ? identity.unit : (template.unitOfMeasure?.unit ?? identity.unit), + unitCustomData: isOcpp16 ? undefined : template.unitOfMeasure?.customData, + unitMultiplier: isOcpp16 ? undefined : template.unitOfMeasure?.multiplier, + }), ]) if (!templatesByIdentity.has(key)) templatesByIdentity.set(key, template) } @@ -1711,7 +1790,12 @@ const expandClockAlignedSnapshotSamples = ( ? [...phasedPowerByLine.values()].reduce( (total, sample) => total + - sample.value * resolveMeterValueUnitDivider(measurand, sample.unitOfMeasure?.unit), + sample.value * + resolveMeterValueUnitScale( + measurand, + sample.unitOfMeasure?.unit, + sample.unitOfMeasure?.multiplier + ), 0 ) : undefined @@ -1721,7 +1805,12 @@ const expandClockAlignedSnapshotSamples = ( } else if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) { if (evseId !== 0 && source != null && preferBaseline) { rawValue = - source.value * resolveMeterValueUnitDivider(measurand, source.unitOfMeasure?.unit) + source.value * + resolveMeterValueUnitScale( + measurand, + source.unitOfMeasure?.unit, + source.unitOfMeasure?.multiplier + ) } else if (phaseFamily === 'Aggregate') { rawValue = energyRegister } else if (phaseFamily === 'Line') { @@ -1749,7 +1838,13 @@ const expandClockAlignedSnapshotSamples = ( ) { continue } else if (source != null && preferBaseline) { - rawValue = source.value * resolveMeterValueUnitDivider(measurand, source.unitOfMeasure?.unit) + rawValue = + source.value * + resolveMeterValueUnitScale( + measurand, + source.unitOfMeasure?.unit, + source.unitOfMeasure?.multiplier + ) if ( (measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT || measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL) && @@ -1782,7 +1877,7 @@ const expandClockAlignedSnapshotSamples = ( if (idle) { rawValue = 0 } else { - const divider = resolveMeterValueUnitDivider(measurand, template.unit as string | undefined) + const divider = resolveMeterValueUnitDivider(measurand, template.unit) const maximumValue = measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT ? chargingStation.getConnectorMaximumAvailablePower(connectorId, evseId) / @@ -1822,7 +1917,13 @@ const expandClockAlignedSnapshotSamples = ( ) { rawValue = randomInt(template.minimumValue ?? 0, Constants.SOC_MAXIMUM_PERCENT + 1) } else if (source != null) { - rawValue = source.value * resolveMeterValueUnitDivider(measurand, source.unitOfMeasure?.unit) + rawValue = + source.value * + resolveMeterValueUnitScale( + measurand, + source.unitOfMeasure?.unit, + source.unitOfMeasure?.multiplier + ) if ( (measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT || measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL) && @@ -1836,7 +1937,7 @@ const expandClockAlignedSnapshotSamples = ( getRandomFloatFluctuatedRounded( convertToFloat(template.value), template.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT - ) * resolveMeterValueUnitDivider(measurand, template.unit as string | undefined) + ) * resolveMeterValueUnitDivider(measurand, template.unit) } else if (measurand === MeterValueMeasurand.VOLTAGE) { const nominal = phaseFamily === 'LineToLine' @@ -1857,8 +1958,7 @@ const expandClockAlignedSnapshotSamples = ( rawValue, preferBaseline ) - const unitValue = - physicalValue / resolveMeterValueUnitDivider(measurand, template.unit as string | undefined) + const unitValue = physicalValue / resolveMeterValueUnitDivider(measurand, template.unit) const value = measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL ? truncateTransactionIntervalValue(unitValue) @@ -1867,18 +1967,29 @@ const expandClockAlignedSnapshotSamples = ( } return applyClockAlignedVoltageControls( chargingStation, - expanded as OCPP20SampledValue[], + expanded, buildVersionedSampledValue, context ) } +const getSampledValueUnit = ( + sampledValue: SampledValue +): NonNullable | OCPP16MeterValueUnit | undefined => + isOCPP20SampledValue(sampledValue) ? sampledValue.unitOfMeasure?.unit : sampledValue.unit + +const getSampledValueCustomData = (sampledValue: SampledValue): CustomDataType | undefined => + isOCPP20SampledValue(sampledValue) ? sampledValue.customData : undefined + +const getSampledValueFormat = (sampledValue: SampledValue): OCPP16MeterValueFormat | undefined => + isOCPP20SampledValue(sampledValue) ? undefined : sampledValue.format + const applyClockAlignedVoltageControls = ( chargingStation: ChargingStation, - sampledValues: OCPP20SampledValue[], + sampledValues: SampledValue[], buildVersionedSampledValue: BuildVersionedSampledValue, context: MeterValueContext | undefined -): OCPP20SampledValue[] => { +): SampledValue[] => { if ( chargingStation.getNumberOfPhases() !== 3 || chargingStation.stationInfo?.currentOutType !== CurrentType.AC @@ -1892,45 +2003,60 @@ const applyClockAlignedVoltageControls = ( const configuredPhaseSamples = sampledValues.filter( sample => sample.measurand === MeterValueMeasurand.VOLTAGE && sample.phase != null ) - const automaticSamples: OCPP20SampledValue[] = [] + const automaticSamples: SampledValue[] = [] for (const aggregateVoltage of aggregateVoltages) { const configuredPhases = new Set( configuredPhaseSamples .filter( sample => sample.context === aggregateVoltage.context && - canonicalizeCustomData(sample.customData) === - canonicalizeCustomData(aggregateVoltage.customData) && - sample.format === aggregateVoltage.format && + canonicalizeCustomData(getSampledValueCustomData(sample)) === + canonicalizeCustomData(getSampledValueCustomData(aggregateVoltage)) && + getSampledValueFormat(sample) === getSampledValueFormat(aggregateVoltage) && sample.location === aggregateVoltage.location && - sample.unitOfMeasure?.unit === aggregateVoltage.unitOfMeasure?.unit + getSampledValueUnit(sample) === getSampledValueUnit(aggregateVoltage) ) .map(sample => sample.phase) ) - const template = { - ...aggregateVoltage, - unit: aggregateVoltage.unitOfMeasure?.unit, - } as SampledValueTemplate - const addPhase = (phase: OCPP20PhaseEnumType, value: number): void => { + const template: SampledValueTemplate = { + ...(isOCPP20SampledValue(aggregateVoltage) + ? { + customData: aggregateVoltage.customData, + unit: getSampledValueUnit(aggregateVoltage), + unitOfMeasure: aggregateVoltage.unitOfMeasure, + } + : { format: aggregateVoltage.format, unit: aggregateVoltage.unit }), + context: aggregateVoltage.context, + location: aggregateVoltage.location, + measurand: aggregateVoltage.measurand, + phase: aggregateVoltage.phase, + } + const addPhase = (phase: MeterValuePhase, value: number): void => { if (!configuredPhases.has(phase)) { automaticSamples.push( - buildVersionedSampledValue( - template, - roundTo(value, 2), - context, - phase - ) as OCPP20SampledValue + buildVersionedSampledValue(template, roundTo(value, 2), context, phase) ) } } - addPhase(OCPP20PhaseEnumType.L1_N, aggregateVoltage.value) - addPhase(OCPP20PhaseEnumType.L2_N, aggregateVoltage.value) - addPhase(OCPP20PhaseEnumType.L3_N, aggregateVoltage.value) + const aggregateValue = + convertToFloat(aggregateVoltage.value) * + resolveMeterValueUnitScale( + MeterValueMeasurand.VOLTAGE, + isOCPP20SampledValue(aggregateVoltage) + ? aggregateVoltage.unitOfMeasure?.unit + : aggregateVoltage.unit, + isOCPP20SampledValue(aggregateVoltage) + ? aggregateVoltage.unitOfMeasure?.multiplier + : undefined + ) + addPhase(MeterValuePhase.L1_N, aggregateValue) + addPhase(MeterValuePhase.L2_N, aggregateValue) + addPhase(MeterValuePhase.L3_N, aggregateValue) if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) { - const lineToLineVoltage = aggregateVoltage.value * Math.sqrt(3) - addPhase(OCPP20PhaseEnumType.L1_L2, lineToLineVoltage) - addPhase(OCPP20PhaseEnumType.L2_L3, lineToLineVoltage) - addPhase(OCPP20PhaseEnumType.L3_L1, lineToLineVoltage) + const lineToLineVoltage = aggregateValue * Math.sqrt(3) + addPhase(MeterValuePhase.L1_L2, lineToLineVoltage) + addPhase(MeterValuePhase.L2_L3, lineToLineVoltage) + addPhase(MeterValuePhase.L3_L1, lineToLineVoltage) } } const retainedSamples = @@ -2050,7 +2176,7 @@ export const buildClockAlignedConnectorMeterValue = ( RequestCommand.METER_VALUES ) } - return buildIdentifiedMeterValue( + const meterValue = buildIdentifiedMeterValue( chargingStation, { ...identity, commitState: identity.commitState ?? true, snapshot: true }, interval, @@ -2058,7 +2184,9 @@ export const buildClockAlignedConnectorMeterValue = ( context, false, OCPP20ComponentName.AlignedDataCtrlr - ) as OCPP20MeterValue + ) + assertMeterValueBuilderResult(meterValue, OCPPVersion.VERSION_201) + return meterValue } const buildIdentifiedMeterValue = ( @@ -2106,24 +2234,31 @@ const buildIdentifiedMeterValue = ( if (snapshot && identity.sampledValueBaseline != null) { const timestamp = identity.timestamp ?? new Date() if (signingConfig != null) signingConfig.timestamp = timestamp - return { - sampledValue: expandClockAlignedSnapshotSamples( - chargingStation, - connectorId, - evseId, - identity.sampledValueBaseline, - buildSignedVersionedSampledValue, - measurandsKey, - context, - registerValuesWithoutPhases, - identity.idle ?? true, - identity.transactionId != null, - identity.energyRegisterWhOverride, - true, - identity.sampledValueTemplates - ), - timestamp, - } as OCPP20MeterValue + const sampledValue = expandClockAlignedSnapshotSamples( + chargingStation, + connectorId, + evseId, + identity.sampledValueBaseline, + buildSignedVersionedSampledValue, + measurandsKey, + context, + registerValuesWithoutPhases, + identity.idle ?? true, + identity.transactionId != null, + identity.energyRegisterWhOverride, + true, + identity.sampledValueTemplates + ) + if (isOCPP20x(chargingStation.stationInfo?.ocppVersion)) { + if (!sampledValue.every(isOCPP20SampledValue)) { + throw new BaseError('Clock-aligned snapshot contains OCPP 1.6 SampledValues for OCPP 2.0.x') + } + return { sampledValue, timestamp } + } + if (!sampledValue.every(isOCPP16SampledValue)) { + throw new BaseError('Clock-aligned snapshot contains OCPP 2.0.x SampledValues for OCPP 1.6') + } + return { sampledValue, timestamp } } const connectorStatus = chargingStation.getConnectorStatus(connectorId, evseId) const transactionBegin = context === MeterValueContext.TRANSACTION_BEGIN @@ -2177,13 +2312,23 @@ const buildIdentifiedMeterValue = ( coherentMeterValue.timestamp } if (snapshot) { - const coherentOcpp20MeterValue = coherentMeterValue as OCPP20MeterValue - coherentOcpp20MeterValue.sampledValue = applyClockAlignedVoltageControls( + const version = chargingStation.stationInfo?.ocppVersion + const controlledSampledValues = applyClockAlignedVoltageControls( chargingStation, - coherentOcpp20MeterValue.sampledValue, + coherentMeterValue.sampledValue, buildSignedVersionedSampledValue, context ) + const controlledMeterValue = { + ...coherentMeterValue, + sampledValue: controlledSampledValues, + } as MeterValue + if (version === OCPPVersion.VERSION_16) { + assertMeterValueBuilderResult(controlledMeterValue, OCPPVersion.VERSION_16) + } else { + assertMeterValueBuilderResult(controlledMeterValue, OCPPVersion.VERSION_201) + } + coherentMeterValue.sampledValue = controlledMeterValue.sampledValue } // Only transactional builds may flip the one-time public-key flag. A // coherent session always carries a transactionId, but guard explicitly to @@ -2262,7 +2407,7 @@ const buildIdentifiedMeterValue = ( connectorId, meterValue, voltageMeasurand, - `L${phase.toString()}-N` as MeterValuePhase, + LINE_TO_NEUTRAL_PHASES[phase - 1], chargingStation.getVoltageOut(), buildVersionedSampledValue, measurandsKey, @@ -2272,11 +2417,7 @@ const buildIdentifiedMeterValue = ( snapshot ) if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues === true) { - const nextPhase = - (phase + 1) % chargingStation.getNumberOfPhases() !== 0 - ? ((phase + 1) % chargingStation.getNumberOfPhases()).toString() - : chargingStation.getNumberOfPhases().toString() - const lineToLineLabel = `L${phase.toString()}-L${nextPhase}` as MeterValuePhase + const lineToLineLabel = LINE_TO_LINE_PHASES[phase - 1] // `V_LL = sqrt(3) * V_LN` in a balanced 3-phase Y system; the // sqrt(3) factor comes from the 30-degree phase separation, not // from the phase count itself. Emitting L-L values makes physical @@ -2347,7 +2488,7 @@ const buildIdentifiedMeterValue = ( `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates ] if (phaseTemplate != null) { - const phaseValue = `L${phase.toString()}-N` as MeterValuePhase + const phaseValue = LINE_TO_NEUTRAL_PHASES[phase - 1] const phasePowerValue = powerMeasurand.values[`L${phase.toString()}` as keyof MeasurandValues] meterValue.sampledValue.push( @@ -2415,7 +2556,7 @@ const buildIdentifiedMeterValue = ( chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases(); phase++ ) { - const phaseValue = `L${phase.toString()}` as MeterValuePhase + const phaseValue = LINE_PHASES[phase - 1] meterValue.sampledValue.push( buildVersionedSampledValue( currentMeasurand.perPhaseTemplates[ @@ -2658,7 +2799,7 @@ const buildIdentifiedMeterValue = ( chargingStation, connectorId, evseId, - meterValue.sampledValue as OCPP20SampledValue[], + meterValue.sampledValue.map(toSnapshotBaselineSample), buildSignedVersionedSampledValue, measurandsKey, context, @@ -2892,7 +3033,7 @@ export const resolveSampledValueFields = ( location: MeterValueLocation | undefined measurand: MeterValueMeasurand phase: MeterValuePhase | undefined - unit: MeterValueUnit | undefined + unit: string | undefined value: number } => { const sampledValueMeasurand = @@ -2902,9 +3043,7 @@ export const resolveSampledValueFields = ( location: sampledValueTemplate.location ?? getMeasurandDefaultLocation(sampledValueMeasurand), measurand: sampledValueMeasurand, phase: phase ?? sampledValueTemplate.phase, - unit: - (sampledValueTemplate.unit as MeterValueUnit | undefined) ?? - getMeasurandDefaultUnit(sampledValueMeasurand), + unit: sampledValueTemplate.unit ?? getMeasurandDefaultUnit(sampledValueMeasurand), value, } } diff --git a/src/charging-station/ocpp/OCPPSignedMeterValueUtils.ts b/src/charging-station/ocpp/OCPPSignedMeterValueUtils.ts index 318537dc..17815104 100644 --- a/src/charging-station/ocpp/OCPPSignedMeterValueUtils.ts +++ b/src/charging-station/ocpp/OCPPSignedMeterValueUtils.ts @@ -9,6 +9,7 @@ import { type SampledValue, SigningMethodEnumType, } from '../../types/index.js' +import { getEnumStringValue } from '../../utils/index.js' import { getErrorMessage, isJsonObject, isNotEmptyString, logger } from '../../utils/index.js' export const getOCPP16SignedMeterValuePublicKey = ( @@ -146,8 +147,16 @@ export interface SigningPrerequisiteSuccess { export const validateSigningPrerequisites = ( publicKeyHex: string | undefined, - configuredSigningMethod: SigningMethodEnumType | undefined + configuredSigningMethod: string | undefined ): SigningPrerequisiteResult | SigningPrerequisiteSuccess => { + const signingMethod = getEnumStringValue(SigningMethodEnumType, configuredSigningMethod) + if (configuredSigningMethod !== undefined && signingMethod == null) { + return { + enabled: false, + reason: `SigningMethod '${configuredSigningMethod}' is invalid`, + } + } + if (!isNotEmptyString(publicKeyHex)) { return { enabled: false, reason: 'Public key is not configured' } } @@ -161,28 +170,23 @@ export const validateSigningPrerequisites = ( } } - if (configuredSigningMethod != null && configuredSigningMethod !== derivedMethod) { + if (signingMethod != null && signingMethod !== derivedMethod) { return { enabled: false, reason: - `SigningMethod mismatch: configured '${configuredSigningMethod}' ` + + `SigningMethod mismatch: configured '${signingMethod}' ` + `but public key uses '${derivedMethod}'`, } } - return { enabled: true, signingMethod: configuredSigningMethod ?? derivedMethod } + return { enabled: true, signingMethod: signingMethod ?? derivedMethod } } -const PUBLIC_KEY_WITH_SIGNED_METER_VALUE_VALUES = new Set( - Object.values(PublicKeyWithSignedMeterValueEnumType) -) - export const parsePublicKeyWithSignedMeterValue = ( value: string | undefined ): PublicKeyWithSignedMeterValueEnumType => - value != null && PUBLIC_KEY_WITH_SIGNED_METER_VALUE_VALUES.has(value) - ? (value as PublicKeyWithSignedMeterValueEnumType) - : PublicKeyWithSignedMeterValueEnumType.Never + getEnumStringValue(PublicKeyWithSignedMeterValueEnumType, value) ?? + PublicKeyWithSignedMeterValueEnumType.Never export const shouldIncludePublicKey = ( config: PublicKeyWithSignedMeterValueEnumType, diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index 3ec2ea9e..d232bc29 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -9,17 +9,23 @@ import { BaseError } from '../../exception/index.js' import { ApplicationProtocolVersion, MapStringifyFormat, - type ProcedureName, - type Protocol, + ProcedureName, + Protocol, type ProtocolRequest, type ProtocolResponse, - type ProtocolVersion, + ProtocolVersion, type RequestPayload, ResponseStatus, type UIServerConfiguration, type UUIDv4, } from '../../types/index.js' -import { generateUUID, getErrorMessage, JSONStringify, logger } from '../../utils/index.js' +import { + generateUUID, + getEnumStringValue, + getErrorMessage, + JSONStringify, + logger, +} from '../../utils/index.js' import { AbstractUIServer } from './AbstractUIServer.js' import { DEFAULT_COMPRESSION_THRESHOLD_BYTES, @@ -208,11 +214,17 @@ export class UIHttpServer extends AbstractUIServer { `Malformed URL path: '${pathname}' (expected /ui/:version/:procedureName)` ) } - const [protocol, version, procedureName] = parts as [Protocol, ProtocolVersion, ProcedureName] - const fullProtocol = `${protocol}${version}` - if (!isProtocolAndVersionSupported(fullProtocol)) { + const [rawProtocol, rawVersion, rawProcedureName] = parts + const protocol = getEnumStringValue(Protocol, rawProtocol) + const version = getEnumStringValue(ProtocolVersion, rawVersion) + const procedureName = getEnumStringValue(ProcedureName, rawProcedureName) + const fullProtocol = `${rawProtocol}${rawVersion}` + if (protocol == null || version == null || !isProtocolAndVersionSupported(fullProtocol)) { throw new BaseError(`Unsupported UI protocol version: '${fullProtocol}'`) } + if (procedureName == null) { + throw new BaseError(`Unsupported UI procedure: '${rawProcedureName}'`) + } this.registerProtocolVersionUIService(version) req.on('error', error => { @@ -251,7 +263,12 @@ export class UIHttpServer extends AbstractUIServer { `${this.logPrefix(moduleName, 'requestListener')} Handle HTTP request error:`, error ) - this.sendResponse(this.buildProtocolResponse(uuid, { status: ResponseStatus.FAILURE })) + this.sendResponse( + this.buildProtocolResponse(uuid, { + errorMessage: getErrorMessage(error), + status: ResponseStatus.FAILURE, + }) + ) } } diff --git a/src/charging-station/ui-server/UIServerUtils.ts b/src/charging-station/ui-server/UIServerUtils.ts index 52ec53a1..d237b6af 100644 --- a/src/charging-station/ui-server/UIServerUtils.ts +++ b/src/charging-station/ui-server/UIServerUtils.ts @@ -1,7 +1,7 @@ import type { IncomingMessage } from 'node:http' import { Protocol, ProtocolVersion } from '../../types/index.js' -import { isEmpty, logger, logPrefix } from '../../utils/index.js' +import { getEnumStringValue, isEmpty, logger, logPrefix } from '../../utils/index.js' export enum HttpMethod { DELETE = 'DELETE', @@ -57,10 +57,7 @@ export const isProtocolAndVersionSupported = (protocolStr: string): boolean => { if (protocolAndVersion == null) { return false } - const [protocol, version] = protocolAndVersion - return ( - Object.values(Protocol).includes(protocol) && Object.values(ProtocolVersion).includes(version) - ) + return true } export const getProtocolAndVersion = ( @@ -73,10 +70,16 @@ export const getProtocolAndVersion = ( return undefined } const protocolIndex = protocolStr.indexOf(Protocol.UI) - const protocol = protocolStr.substring(protocolIndex, protocolIndex + Protocol.UI.length) - const version = protocolStr.substring(protocolIndex + Protocol.UI.length) - if (isEmpty(protocol) || isEmpty(version)) { + const protocol = getEnumStringValue( + Protocol, + protocolStr.substring(protocolIndex, protocolIndex + Protocol.UI.length) + ) + const version = getEnumStringValue( + ProtocolVersion, + protocolStr.substring(protocolIndex + Protocol.UI.length) + ) + if (protocol == null || version == null) { return undefined } - return [protocol, version] as [Protocol, ProtocolVersion] + return [protocol, version] } diff --git a/src/exception/OCPPError.ts b/src/exception/OCPPError.ts index 4f5477aa..75f92b06 100644 --- a/src/exception/OCPPError.ts +++ b/src/exception/OCPPError.ts @@ -1,6 +1,6 @@ // Partial Copyright Jerome Benoit. 2021-2025. All Rights Reserved. -import type { ErrorType, IncomingRequestCommand, JsonType, RequestCommand } from '../types/index.js' +import type { ErrorType, JsonType, OCPPCommandName } from '../types/index.js' // Direct path: the `ocpp/index.js` barrel pulls OCPP20ServiceUtils which transitively imports OCPPError, causing a TDZ cycle. import { OCPPConstants } from '../charging-station/ocpp/OCPPConstants.js' @@ -8,16 +8,11 @@ import { BaseError } from './BaseError.js' export class OCPPError extends BaseError { code: ErrorType - command: IncomingRequestCommand | RequestCommand + command: OCPPCommandName details?: JsonType public override readonly name = 'OCPPError' as const - constructor ( - code: ErrorType, - message: string, - command?: IncomingRequestCommand | RequestCommand, - details?: JsonType - ) { + constructor (code: ErrorType, message: string, command?: OCPPCommandName, details?: JsonType) { super(message) this.code = code diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index d19aebb3..325f9384 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -10,11 +10,9 @@ import { parentPort } from 'node:worker_threads' import { BaseError } from '../exception/index.js' import { ConfigurationSection, - type IncomingRequestCommand, type LogConfiguration, MapStringifyFormat, MessageType, - type RequestCommand, type Statistics, type StatisticsData, type StorageConfiguration, @@ -127,10 +125,7 @@ export class PerformanceStatistics { return logPrefix(' Performance statistics') } - public addRequestStatistic ( - command: IncomingRequestCommand | RequestCommand, - messageType: MessageType - ): void { + public addRequestStatistic (command: string, messageType: MessageType): void { switch (messageType) { case MessageType.CALL_ERROR_MESSAGE: { const commandStatisticsData = this.statistics.statisticsData.get(command) @@ -282,7 +277,7 @@ export class PerformanceStatistics { ...this.statistics, statisticsData: JSON.parse( JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object) - ) as Map, + ) as Map, }) } diff --git a/src/types/MeasurandPerPhaseSampledValueTemplates.ts b/src/types/MeasurandPerPhaseSampledValueTemplates.ts index 7c262192..5b4c627f 100644 --- a/src/types/MeasurandPerPhaseSampledValueTemplates.ts +++ b/src/types/MeasurandPerPhaseSampledValueTemplates.ts @@ -1,4 +1,17 @@ -import type { SampledValue } from './ocpp/MeterValues.js' +import type { JsonObject } from './JsonType.js' +import type { OCPP16MeterValueFormat, OCPP16SampledValue } from './ocpp/1.6/MeterValues.js' +import type { CustomDataType } from './ocpp/2.0/Common.js' +import type { + OCPP20SampledValue, + OCPP20SignedMeterValue, + OCPP20UnitOfMeasure, +} from './ocpp/2.0/MeterValues.js' +import type { + MeterValueContext, + MeterValueLocation, + MeterValueMeasurand, + MeterValuePhase, +} from './ocpp/MeterValues.js' export interface MeasurandPerPhaseSampledValueTemplates { L1?: SampledValueTemplate @@ -6,7 +19,17 @@ export interface MeasurandPerPhaseSampledValueTemplates { L3?: SampledValueTemplate } -export type SampledValueTemplate = SampledValue & { +export interface SampledValueTemplate extends JsonObject { + context?: MeterValueContext + customData?: CustomDataType fluctuationPercent?: number + format?: OCPP16MeterValueFormat + location?: MeterValueLocation + measurand?: MeterValueMeasurand minimumValue?: number + phase?: MeterValuePhase + signedMeterValue?: OCPP20SignedMeterValue + unit?: NonNullable + unitOfMeasure?: OCPP20UnitOfMeasure + value?: OCPP16SampledValue['value'] | OCPP20SampledValue['value'] } diff --git a/src/types/Statistics.ts b/src/types/Statistics.ts index fc979871..f4dffa05 100644 --- a/src/types/Statistics.ts +++ b/src/types/Statistics.ts @@ -1,13 +1,12 @@ import type { CircularBuffer } from 'mnemonist' import type { WorkerData } from '../worker/index.js' -import type { IncomingRequestCommand, RequestCommand } from './ocpp/Requests.js' export interface Statistics extends WorkerData { createdAt: Date id: string name: string - statisticsData: Map + statisticsData: Map updatedAt?: Date uri: string } diff --git a/src/types/index.ts b/src/types/index.ts index 1f940391..273d8927 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -347,6 +347,7 @@ export { ChargingRateUnitType, type ChargingSchedule, type ChargingSchedulePeriod, + isOCPP16ChargingProfile, RecurrencyKindType, } from './ocpp/ChargingProfile.js' export { type GenericResponse, GenericStatus, RegistrationStatusEnumType } from './ocpp/Common.js' @@ -366,6 +367,10 @@ export { ConnectorStatusEnum, type ConnectorStatusTransition } from './ocpp/Conn export { ErrorType } from './ocpp/ErrorType.js' export { MessageType } from './ocpp/MessageType.js' export { + isOCPP16MeterValue, + isOCPP16SampledValue, + isOCPP20MeterValue, + isOCPP20SampledValue, type MeterValue, MeterValueContext, MeterValueLocation, @@ -391,6 +396,8 @@ export { type IncomingRequestHandler, MessageTrigger, type MeterValuesRequest, + type OCPPCommand, + type OCPPCommandName, type OutgoingRequest, type PendingRequestCancellationCallback, type PendingRequestCancellationOptions, @@ -400,6 +407,7 @@ export { type ResponseType, type StatusNotificationOptions, type StatusNotificationRequest, + UNKNOWN_OCPP_COMMAND, } from './ocpp/Requests.js' export { type Reservation, diff --git a/src/types/ocpp/1.6/MeterValues.ts b/src/types/ocpp/1.6/MeterValues.ts index 6bcd4b7f..b14d2c66 100644 --- a/src/types/ocpp/1.6/MeterValues.ts +++ b/src/types/ocpp/1.6/MeterValues.ts @@ -73,6 +73,7 @@ export enum OCPP16MeterValueUnit { KILO_WATT = 'kW', KILO_WATT_HOUR = 'kWh', PERCENT = 'Percent', + TEMP_CELCIUS = 'Celcius', TEMP_CELSIUS = 'Celsius', TEMP_FAHRENHEIT = 'Fahrenheit', TEMP_KELVIN = 'K', diff --git a/src/types/ocpp/ChargingProfile.ts b/src/types/ocpp/ChargingProfile.ts index 11336f16..80dbf254 100644 --- a/src/types/ocpp/ChargingProfile.ts +++ b/src/types/ocpp/ChargingProfile.ts @@ -19,6 +19,12 @@ import { export type ChargingProfile = OCPP16ChargingProfile | OCPP20ChargingProfileType +export const isOCPP16ChargingProfile = ( + chargingProfile: ChargingProfile +): chargingProfile is OCPP16ChargingProfile => + typeof chargingProfile.chargingProfileId === 'number' && + !Array.isArray(chargingProfile.chargingSchedule) + export type ChargingSchedule = OCPP16ChargingSchedule | OCPP20ChargingScheduleType export type ChargingSchedulePeriod = OCPP16ChargingSchedulePeriod | OCPP20ChargingSchedulePeriodType diff --git a/src/types/ocpp/MeterValues.ts b/src/types/ocpp/MeterValues.ts index 988b7327..1796abfc 100644 --- a/src/types/ocpp/MeterValues.ts +++ b/src/types/ocpp/MeterValues.ts @@ -19,6 +19,20 @@ import { export type MeterValue = OCPP16MeterValue | OCPP20MeterValue +export const isOCPP16SampledValue = ( + sampledValue: OCPP16SampledValue | OCPP20SampledValue +): sampledValue is OCPP16SampledValue => typeof sampledValue.value === 'string' + +export const isOCPP20SampledValue = ( + sampledValue: OCPP16SampledValue | OCPP20SampledValue +): sampledValue is OCPP20SampledValue => typeof sampledValue.value === 'number' + +export const isOCPP16MeterValue = (meterValue: MeterValue): meterValue is OCPP16MeterValue => + meterValue.sampledValue.length === 0 || meterValue.sampledValue.every(isOCPP16SampledValue) + +export const isOCPP20MeterValue = (meterValue: MeterValue): meterValue is OCPP20MeterValue => + meterValue.sampledValue.length > 0 && meterValue.sampledValue.every(isOCPP20SampledValue) + export const MeterValueUnit = { ...OCPP16MeterValueUnit, ...OCPP20UnitEnumType, diff --git a/src/types/ocpp/Requests.ts b/src/types/ocpp/Requests.ts index c2df63d0..ac5cbc0e 100644 --- a/src/types/ocpp/Requests.ts +++ b/src/types/ocpp/Requests.ts @@ -100,6 +100,11 @@ export const RequestCommand = { ...OCPP16RequestCommand, ...OCPP20RequestCommand, } as const +export type OCPPCommand = IncomingRequestCommand | RequestCommand + +export const UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' +export type OCPPCommandName = `${OCPPCommand}` | typeof UNKNOWN_OCPP_COMMAND + // eslint-disable-next-line @typescript-eslint/no-redeclare export type RequestCommand = OCPP16RequestCommand | OCPP20RequestCommand diff --git a/src/utils/EnumUtils.ts b/src/utils/EnumUtils.ts new file mode 100644 index 00000000..f2f48ef5 --- /dev/null +++ b/src/utils/EnumUtils.ts @@ -0,0 +1,10 @@ +export const getEnumStringValue = ( + enumObject: Readonly>, + value: unknown +): T | undefined => { + for (const key in enumObject) { + if (Object.hasOwn(enumObject, key) && enumObject[key] === value) { + return enumObject[key] + } + } +} diff --git a/src/utils/ErrorUtils.ts b/src/utils/ErrorUtils.ts index dbcfb6d5..77b51379 100644 --- a/src/utils/ErrorUtils.ts +++ b/src/utils/ErrorUtils.ts @@ -9,7 +9,7 @@ import type { IncomingRequestCommand, JsonType, MessageType, - RequestCommand, + OCPPCommandName, } from '../types/index.js' import { logger } from './Logger.js' @@ -104,7 +104,7 @@ export const handleFileException = ( export const handleSendMessageError = ( chargingStation: ChargingStation, - commandName: IncomingRequestCommand | RequestCommand, + commandName: OCPPCommandName, messageType: MessageType, error: Error, params?: HandleErrorParams diff --git a/src/utils/index.ts b/src/utils/index.ts index 300a68d9..2cb7b800 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -33,6 +33,7 @@ export { export { ConfigurationValidationError, validateConfiguration } from './ConfigurationValidation.js' export { Constants } from './Constants.js' export { ACElectricUtils, DCElectricUtils } from './ElectricUtils.js' +export { getEnumStringValue } from './EnumUtils.js' export { ensureError, getErrorMessage, diff --git a/tests/charging-station/ChargingStation-Configuration.test.ts b/tests/charging-station/ChargingStation-Configuration.test.ts index a3c1b24d..03a45241 100644 --- a/tests/charging-station/ChargingStation-Configuration.test.ts +++ b/tests/charging-station/ChargingStation-Configuration.test.ts @@ -13,6 +13,7 @@ import { Constants } from '../../src/utils/index.js' import { standardCleanup, withMockTimers } from '../helpers/TestLifecycleHelpers.js' import { TEST_HEARTBEAT_INTERVAL_MS, TEST_ONE_HOUR_MS } from './ChargingStationTestConstants.js' import { cleanupChargingStation, createMockChargingStation } from './helpers/StationHelpers.js' +import { WebSocketReadyState } from './mocks/MockWebSocket.js' await describe('ChargingStation Configuration Management', async () => { // ===== B02/B03 BOOT NOTIFICATION BEHAVIOR TESTS ===== @@ -460,7 +461,7 @@ await describe('ChargingStation Configuration Management', async () => { assert.strictEqual(station.isWebSocketConnectionOpened(), true) // Act - change ready state to CLOSED - mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Assert assert.strictEqual(station.isWebSocketConnectionOpened(), false) @@ -473,7 +474,7 @@ await describe('ChargingStation Configuration Management', async () => { const mocks = result.mocks // Act - mocks.webSocket.readyState = 0 // WebSocketReadyState.CONNECTING + mocks.webSocket.readyState = WebSocketReadyState.CONNECTING // Assert assert.strictEqual(station.isWebSocketConnectionOpened(), false) @@ -486,7 +487,7 @@ await describe('ChargingStation Configuration Management', async () => { const mocks = result.mocks // Act - mocks.webSocket.readyState = 2 // WebSocketReadyState.CLOSING + mocks.webSocket.readyState = WebSocketReadyState.CLOSING // Assert assert.strictEqual(station.isWebSocketConnectionOpened(), false) @@ -632,7 +633,7 @@ await describe('ChargingStation Configuration Management', async () => { let openEventFired = false // First close the connection to test opening - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Set up listener mocks.webSocket.on('open', () => { @@ -644,7 +645,7 @@ await describe('ChargingStation Configuration Management', async () => { // Assert assert.strictEqual(openEventFired, true) - assert.strictEqual(mocks.webSocket.readyState, 1) // WebSocketReadyState.OPEN + assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.OPEN) }) await it('should emit close event and set readyState via simulateClose()', () => { @@ -664,7 +665,7 @@ await describe('ChargingStation Configuration Management', async () => { // Assert assert.strictEqual(closeCode, 1001) - assert.strictEqual(mocks.webSocket.readyState, 3) // WebSocketReadyState.CLOSED + assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.CLOSED) }) await it('should emit error event via simulateError()', () => { @@ -741,7 +742,7 @@ await describe('ChargingStation Configuration Management', async () => { const mocks = result.mocks // Close the WebSocket - mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act & Assert assert.throws( diff --git a/tests/charging-station/ChargingStation-Lifecycle.test.ts b/tests/charging-station/ChargingStation-Lifecycle.test.ts index 8ce43b44..750b4b8b 100644 --- a/tests/charging-station/ChargingStation-Lifecycle.test.ts +++ b/tests/charging-station/ChargingStation-Lifecycle.test.ts @@ -19,8 +19,10 @@ import { OCPP20RequestService } from '../../src/charging-station/ocpp/2.0/OCPP20 import { OCPP20ResponseService } from '../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js' import { OCPP20ServiceUtils } from '../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js' import { stopRunningTransactions } from '../../src/charging-station/ocpp/OCPPServiceOperations.js' +import { OCPPError } from '../../src/exception/index.js' import { AvailabilityType, + ErrorType, MessageType, OCPP16AuthorizationStatus, OCPP16ChargePointStatus, @@ -38,7 +40,7 @@ import { OCPP20TriggerReasonEnumType, OCPPVersion, } from '../../src/types/index.js' -import { AsyncLock, AsyncLockType, Constants } from '../../src/utils/index.js' +import { AsyncLock, AsyncLockType, Constants, logger } from '../../src/utils/index.js' import { flushMicrotasks, setupConnectorWithTransaction, @@ -2119,6 +2121,78 @@ await describe('ChargingStation Lifecycle', async () => { assert.strictEqual(closeConnection.mock.callCount(), 1) }) + await it('should reject an unknown OCPP command before typed dispatch', async () => { + const sendError = mock.fn((..._args: unknown[]): Promise => Promise.resolve()) + const { station: activeStation } = createMockChargingStation({ + ocppRequestService: { sendError }, + ocppVersion: OCPPVersion.VERSION_20, + started: true, + }) + station = activeStation + const stationLifecycle = activeStation as unknown as { + onMessage: ( + data: string, + sourceConnection?: NonNullable + ) => Promise + } + stationLifecycle.onMessage = ( + ChargingStation.prototype as unknown as { + onMessage: typeof stationLifecycle.onMessage + } + ).onMessage + const sourceConnection = activeStation.wsConnection + assert.ok(sourceConnection != null) + + await stationLifecycle.onMessage.call( + activeStation, + JSON.stringify([MessageType.CALL_MESSAGE, 'unknown-command', 'UnknownCommand', {}]), + sourceConnection + ) + + assert.strictEqual(sendError.mock.callCount(), 1) + const error: unknown = sendError.mock.calls[0]?.arguments[2] + assert.ok(error instanceof OCPPError) + assert.strictEqual(error.code, ErrorType.NOT_IMPLEMENTED) + assert.deepStrictEqual(error.details, { command: 'UnknownCommand' }) + }) + + await it('should report the parsed unknown OCPP message type', async t => { + const { station: activeStation } = createMockChargingStation({ + ocppVersion: OCPPVersion.VERSION_20, + started: true, + }) + station = activeStation + const errorMock = t.mock.method(logger, 'error', () => undefined) + const stationLifecycle = activeStation as unknown as { + onMessage: ( + data: string, + sourceConnection?: NonNullable + ) => Promise + } + stationLifecycle.onMessage = ( + ChargingStation.prototype as unknown as { + onMessage: typeof stationLifecycle.onMessage + } + ).onMessage + const sourceConnection = activeStation.wsConnection + assert.ok(sourceConnection != null) + + await stationLifecycle.onMessage.call( + activeStation, + JSON.stringify([99, 'unknown-message-type', {}]), + sourceConnection + ) + + assert.ok( + errorMock.mock.calls.some(call => { + const loggedMessage: unknown = call.arguments[0] + return ( + typeof loggedMessage === 'string' && loggedMessage.includes('Wrong message type 99') + ) + }) + ) + }) + await it('should drop a late CALL at the websocket boundary without buffering and accepts it after restart', async () => { const transactionId = '00000000-0000-4000-8000-000000000027' const requestHandler = mock.fn((...args: unknown[]): Promise> => { diff --git a/tests/charging-station/ChargingStation-Resilience.test.ts b/tests/charging-station/ChargingStation-Resilience.test.ts index 6a3cb5be..9ac467ca 100644 --- a/tests/charging-station/ChargingStation-Resilience.test.ts +++ b/tests/charging-station/ChargingStation-Resilience.test.ts @@ -11,6 +11,7 @@ import { RegistrationStatusEnumType, RequestCommand } from '../../src/types/inde import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' import { TEST_HEARTBEAT_INTERVAL_MS } from './ChargingStationTestConstants.js' import { cleanupChargingStation, createMockChargingStation } from './helpers/StationHelpers.js' +import { WebSocketReadyState } from './mocks/MockWebSocket.js' await describe('ChargingStation Resilience', async () => { await describe('Error Recovery and Resilience', async () => { @@ -45,7 +46,7 @@ await describe('ChargingStation Resilience', async () => { mocks.webSocket.simulateClose(1006, 'Connection lost') // Assert - WebSocket should be in CLOSED state - assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED + assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.CLOSED) }) await it('should track connection retry count', () => { @@ -183,7 +184,7 @@ await describe('ChargingStation Resilience', async () => { // Assert - Station should remain in valid state assert.strictEqual(station.getNumberOfConnectors() > 0, true) - assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED + assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.CLOSED) }) await it('should handle boot notification rejected state', () => { @@ -319,7 +320,7 @@ await describe('ChargingStation Resilience', async () => { const testMessage = '[2,"test-msg-1","BootNotification",{}]' // Ensure WebSocket is closed - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer a message station.bufferMessage(testMessage) @@ -339,7 +340,7 @@ await describe('ChargingStation Resilience', async () => { const testMessage = '[2,"test-msg-2","Heartbeat",{}]' // Ensure WebSocket is open - mocks.webSocket.readyState = 1 // OPEN + mocks.webSocket.readyState = WebSocketReadyState.OPEN mocks.webSocket.simulateOpen() // Act - Send message @@ -361,7 +362,7 @@ await describe('ChargingStation Resilience', async () => { const msg3 = '[2,"msg-3","StatusNotification",{}]' // Simulate offline: close the connection - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer multiple messages station.bufferMessage(msg1) @@ -390,7 +391,7 @@ await describe('ChargingStation Resilience', async () => { '[2,"m5","Cmd5",{}]', ] - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer all messages for (const msg of messages) { @@ -412,7 +413,7 @@ await describe('ChargingStation Resilience', async () => { const mocks = result.mocks const messageCount = 100 - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer many messages for (let i = 0; i < messageCount; i++) { @@ -445,7 +446,7 @@ await describe('ChargingStation Resilience', async () => { const mocks = result.mocks const testMessage = '[2,"offline-msg","Test",{}]' - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer message station.bufferMessage(testMessage) @@ -466,7 +467,7 @@ await describe('ChargingStation Resilience', async () => { const mocks = result.mocks const testMessage = '[2,"clear-test","Command",{}]' - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Act - Buffer message station.bufferMessage(testMessage) @@ -497,7 +498,7 @@ await describe('ChargingStation Resilience', async () => { // Act - Perform multiple buffer/disconnect cycles for (let cycle = 0; cycle < cycleCount; cycle++) { // Simulate disconnection - mocks.webSocket.readyState = 3 // CLOSED + mocks.webSocket.readyState = WebSocketReadyState.CLOSED // Buffer messages in this cycle for (let i = 0; i < messagesPerCycle; i++) { diff --git a/tests/charging-station/Helpers.test.ts b/tests/charging-station/Helpers.test.ts index 5f44a71a..a5763b70 100644 --- a/tests/charging-station/Helpers.test.ts +++ b/tests/charging-station/Helpers.test.ts @@ -41,7 +41,6 @@ import { CurrentType, OCPPVersion, type Reservation, - type SampledValueTemplate, } from '../../src/types/index.js' import { logger } from '../../src/utils/index.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' @@ -977,7 +976,7 @@ await describe('Helpers', async () => { const connectorStatus: ConnectorStatus = { availability: AvailabilityType.Operative, energyActiveImportIntervalBaselines: { 'station:aligned': 42 }, - MeterValues: [{} as unknown as SampledValueTemplate], + MeterValues: [{}], status: ConnectorStatusEnum.Available, transactionEnergyActiveImportIntervalBaselines: { transaction: 21 }, transactionStarted: true, diff --git a/tests/charging-station/TransactionIntervalUtils.test.ts b/tests/charging-station/TransactionIntervalUtils.test.ts index b4aa964b..ed0a7bc9 100644 --- a/tests/charging-station/TransactionIntervalUtils.test.ts +++ b/tests/charging-station/TransactionIntervalUtils.test.ts @@ -5,8 +5,6 @@ import assert from 'node:assert/strict' import { afterEach, describe, it } from 'node:test' -import type { ConnectorStatus } from '../../src/types/index.js' - import { captureTransactionIntervalState, completeTransactionIntervalState, @@ -15,6 +13,13 @@ import { restoreTransactionIntervalState, truncateTransactionIntervalValue, } from '../../src/charging-station/meter-values/index.js' +import { + type ConnectorStatus, + MeterValueLocation, + MeterValueMeasurand, + MeterValuePhase, + MeterValueUnit, +} from '../../src/types/index.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' await describe('TransactionIntervalUtils', async () => { @@ -51,8 +56,8 @@ await describe('TransactionIntervalUtils', async () => { { sampledValue: [ { - measurand: 'Energy.Active.Import.Interval', - unit: 'kWh', + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + unit: MeterValueUnit.KILO_WATT_HOUR, value: '0.01', }, ], @@ -66,9 +71,9 @@ await describe('TransactionIntervalUtils', async () => { { sampledValue: [ { - measurand: 'Energy.Active.Import.Interval', - phase: 'L1', - unitOfMeasure: { unit: 'kWh' }, + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + phase: MeterValuePhase.L1, + unitOfMeasure: { unit: MeterValueUnit.KILO_WATT_HOUR }, value: 0.003, }, ], @@ -82,9 +87,24 @@ await describe('TransactionIntervalUtils', async () => { { sampledValue: [ { - location: 'Inlet', - measurand: 'Energy.Active.Import.Interval', - unit: 'Wh', + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + unitOfMeasure: { multiplier: -1, unit: MeterValueUnit.KILO_WATT_HOUR }, + value: 0.2, + }, + ], + }, + 1 + ), + 20 + ) + assert.strictEqual( + getRepresentedTransactionIntervalEnergyWh( + { + sampledValue: [ + { + location: MeterValueLocation.INLET, + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + unit: MeterValueUnit.WATT_HOUR, value: 100, }, ], @@ -99,10 +119,10 @@ await describe('TransactionIntervalUtils', async () => { { sampledValue: [ { - location: 'Inlet', - measurand: 'Energy.Active.Import.Interval', - phase: 'L1', - unit: 'Wh', + location: MeterValueLocation.INLET, + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_INTERVAL, + phase: MeterValuePhase.L1, + unit: MeterValueUnit.WATT_HOUR, value: 100 / 3, }, ], diff --git a/tests/charging-station/meter-values/CoherentMeterValues.test.ts b/tests/charging-station/meter-values/CoherentMeterValues.test.ts index 1887902f..14f9bb67 100644 --- a/tests/charging-station/meter-values/CoherentMeterValues.test.ts +++ b/tests/charging-station/meter-values/CoherentMeterValues.test.ts @@ -21,7 +21,6 @@ import type { ChargingStationInfo, ConnectorStatus, OCPP20SampledValue, - SampledValue, SampledValueTemplate, } from '../../../src/types/index.js' @@ -36,16 +35,21 @@ import { resolveRootSeed, } from '../../../src/charging-station/meter-values/index.js' import { hashLabel } from '../../../src/charging-station/meter-values/PRNG.js' +import { buildOCPP16SampledValue } from '../../../src/charging-station/ocpp/1.6/OCPP16RequestBuilders.js' import { buildOCPP20SampledValue } from '../../../src/charging-station/ocpp/2.0/OCPP20RequestBuilders.js' import { AvailabilityType, CurrentType, + isOCPP20SampledValue, MeterValueContext, MeterValueLocation, MeterValueMeasurand, MeterValuePhase, MeterValueUnit, + OCPP16MeterValueFormat, + OCPP20MeasurandEnumType, OCPPVersion, + Voltage, } from '../../../src/types/index.js' import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' import { TEST_METER_VALUES_INTERVAL_MS } from '../ChargingStationTestConstants.js' @@ -96,7 +100,7 @@ const buildContext = ( groupUnderEvse?: boolean numberOfPhases?: number siblingConnectorMeterValues?: SampledValueTemplate[] - voltageOut?: number + voltageOut?: Voltage } = {} ): { connectorStatus: ConnectorStatus @@ -106,7 +110,7 @@ const buildContext = ( stationInfo: ChargingStationInfo } => { const numberOfPhases = overrides.numberOfPhases ?? 1 - const voltageOut = overrides.voltageOut ?? 230 + const voltageOut = overrides.voltageOut ?? Voltage.VOLTAGE_230 const evseMax = overrides.evseMaxPowerW ?? 22000 const stationInfo: ChargingStationInfo = { @@ -203,7 +207,7 @@ const templatesFor = ( : measurand === MeterValueMeasurand.POWER_ACTIVE_IMPORT ? MeterValueUnit.WATT : undefined - return (unit != null ? { measurand, unit } : { measurand }) as SampledValueTemplate + return unit != null ? { measurand, unit } : { measurand } }) } @@ -214,13 +218,7 @@ const templatesFor = ( * @param value - Numeric value to be serialized. * @returns Minimal SampledValue with stringified value. */ -const passThroughBuilder: BuildVersionedSampledValue = (template, value): SampledValue => { - return { - ...(template.measurand != null && { measurand: template.measurand }), - ...(template.unit != null && { unit: template.unit as never }), - value: value.toString(), - } as SampledValue -} +const passThroughBuilder: BuildVersionedSampledValue = buildOCPP16SampledValue /** * Creates a coherent session and asserts it is defined. Encapsulates the @@ -266,7 +264,7 @@ const advanceDcEnergyAtLocations = ( measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: MeterValueUnit.WATT_HOUR, value: 0, - })) as SampledValueTemplate[] + })) const meterValue = buildCoherentMeterValue( context, @@ -323,7 +321,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 7400, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -355,7 +353,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -385,7 +383,7 @@ await describe('CoherentMeterValues', async () => { const { connectorStatus, context, sessions } = buildContext({ currentType: CurrentType.DC, evseMaxPowerW: 50000, - voltageOut: 400, + voltageOut: Voltage.VOLTAGE_400, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -549,7 +547,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.DC, evseMaxPowerW: 1000, groupUnderEvse: true, - voltageOut: 400, + voltageOut: Voltage.VOLTAGE_400, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -573,7 +571,7 @@ await describe('CoherentMeterValues', async () => { unit: MeterValueUnit.WATT_HOUR, }, ] - ) as unknown as SampledValueTemplate[] + ) const meterValue = buildCoherentMeterValue( context, @@ -613,7 +611,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.DC, evseMaxPowerW: 1000, groupUnderEvse: true, - voltageOut: 400, + voltageOut: Voltage.VOLTAGE_400, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -634,7 +632,7 @@ await describe('CoherentMeterValues', async () => { unit: measurand === MeterValueMeasurand.VOLTAGE ? MeterValueUnit.VOLT : MeterValueUnit.AMP, })) - ) as unknown as SampledValueTemplate[] + ) const meterValue = buildCoherentMeterValue( context, @@ -861,7 +859,6 @@ await describe('CoherentMeterValues', async () => { { fluctuationPercent: 0, measurand: MeterValueMeasurand.FREQUENCY, - unit: MeterValueUnit.HERTZ, value: '50', }, { @@ -875,7 +872,7 @@ await describe('CoherentMeterValues', async () => { value: '50Hz', }, { measurand: MeterValueMeasurand.CURRENT_EXPORT }, - ] as unknown as SampledValueTemplate[] + ] const enabledMeasurands = new Set([ MeterValueMeasurand.CURRENT_EXPORT, MeterValueMeasurand.FREQUENCY, @@ -981,7 +978,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1033,7 +1030,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1113,7 +1110,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1154,7 +1151,7 @@ await describe('CoherentMeterValues', async () => { const { connectorStatus, context, sessions } = buildContext({ currentType: CurrentType.DC, evseMaxPowerW: 50000, - voltageOut: 400, + voltageOut: Voltage.VOLTAGE_400, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1361,7 +1358,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1374,13 +1371,7 @@ await describe('CoherentMeterValues', async () => { sessions.set(1, session) // Phase-preserving builder: include template.phase and template.measurand // so per-phase emissions can be counted and cross-checked. - const phaseBuilder: BuildVersionedSampledValue = (template, value): SampledValue => - ({ - ...(template.measurand != null && { measurand: template.measurand }), - ...(template.phase != null && { phase: template.phase as never }), - ...(template.unit != null && { unit: template.unit as never }), - value: value.toString(), - }) as SampledValue + const phaseBuilder: BuildVersionedSampledValue = buildOCPP16SampledValue connectorStatus.MeterValues = [ { measurand: MeterValueMeasurand.VOLTAGE, phase: MeterValuePhase.L1_N }, { measurand: MeterValueMeasurand.VOLTAGE, phase: MeterValuePhase.L2_N }, @@ -1404,7 +1395,7 @@ await describe('CoherentMeterValues', async () => { }, { measurand: MeterValueMeasurand.CURRENT_IMPORT, phase: MeterValuePhase.L1 }, { measurand: MeterValueMeasurand.CURRENT_IMPORT, phase: MeterValuePhase.N }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue(context, session, phaseBuilder, { intervalMs: TEST_METER_VALUES_INTERVAL_MS, nowMs: TEST_METER_VALUES_INTERVAL_MS, @@ -1449,7 +1440,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 7400, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1463,7 +1454,7 @@ await describe('CoherentMeterValues', async () => { connectorStatus.MeterValues = [ { measurand: MeterValueMeasurand.VOLTAGE, phase: MeterValuePhase.L1_L2 }, { measurand: MeterValueMeasurand.VOLTAGE, phase: MeterValuePhase.L1_N }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue(context, session, passThroughBuilder, { intervalMs: TEST_METER_VALUES_INTERVAL_MS, nowMs: TEST_METER_VALUES_INTERVAL_MS, @@ -1481,7 +1472,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1500,7 +1491,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L1_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue(context, session, passThroughBuilder, { intervalMs: 3_600_000, nowMs: 3_600_000, @@ -1522,7 +1513,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1555,7 +1546,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L3_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue( context, session, @@ -1587,7 +1578,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1616,7 +1607,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L3_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue(context, session, passThroughBuilder, { intervalMs: 3_600_000, nowMs: 3_600_000, @@ -1640,7 +1631,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1667,7 +1658,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L3_N, unit: MeterValueUnit.WATT, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue( context, session, @@ -1699,7 +1690,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1728,7 +1719,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L3_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue( context, session, @@ -1776,7 +1767,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1820,7 +1811,7 @@ await describe('CoherentMeterValues', async () => { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const mv = buildCoherentMeterValue( context, session, @@ -1859,7 +1850,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -1888,7 +1879,7 @@ await describe('CoherentMeterValues', async () => { phase: MeterValuePhase.L2_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const meterValue = buildCoherentMeterValue( context, @@ -1926,7 +1917,7 @@ await describe('CoherentMeterValues', async () => { connectorStatus.MeterValues = [ { phase: MeterValuePhase.L1_N }, { context: MeterValueContext.TRANSACTION_BEGIN, phase: MeterValuePhase.L2_N }, - { format: 'SignedData', phase: MeterValuePhase.L3_N }, + { format: OCPP16MeterValueFormat.SIGNED_DATA, phase: MeterValuePhase.L3_N }, { location: MeterValueLocation.OUTLET, phase: MeterValuePhase.L1_N, @@ -1936,7 +1927,7 @@ await describe('CoherentMeterValues', async () => { customData: { vendorId: 'sensor-b' }, phase: MeterValuePhase.L2_N, }, - ] as unknown as SampledValueTemplate[] + ] const meterValue = buildCoherentMeterValue( context, @@ -1949,8 +1940,10 @@ await describe('CoherentMeterValues', async () => { true ) const energySamples = meterValue.sampledValue.filter( - sample => sample.measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER - ) as OCPP20SampledValue[] + (sample): sample is OCPP20SampledValue => + isOCPP20SampledValue(sample) && + sample.measurand === OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER + ) assert.deepStrictEqual( energySamples.map(sample => [ @@ -1985,13 +1978,13 @@ await describe('CoherentMeterValues', async () => { const evseTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: MeterValueUnit.PERCENT, - } as unknown as SampledValueTemplate + } const { connectorStatus, context, sessions } = buildContext({ currentType: CurrentType.AC, evseMaxPowerW: 22000, evseMeterValues: [evseTemplate], numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2007,7 +2000,7 @@ await describe('CoherentMeterValues', async () => { { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: MeterValueUnit.WATT_HOUR, - } as unknown as SampledValueTemplate, + }, ] const mv = buildCoherentMeterValue(context, session, passThroughBuilder, { intervalMs: TEST_METER_VALUES_INTERVAL_MS, @@ -2029,13 +2022,13 @@ await describe('CoherentMeterValues', async () => { const connectorTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: MeterValueUnit.PERCENT, - } as unknown as SampledValueTemplate + } // No evseMeterValues override => getEvseIdByConnectorId returns undefined. const { connectorStatus, context, sessions } = buildContext({ currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2067,7 +2060,7 @@ await describe('CoherentMeterValues', async () => { const connectorTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: MeterValueUnit.PERCENT, - } as unknown as SampledValueTemplate + } // evseMeterValues=[] => getEvseIdByConnectorId returns 1, getEvseStatus(1).MeterValues = []. // resolveTemplates must skip the empty EVSE array and fall back to connector-level. const { connectorStatus, context, sessions } = buildContext({ @@ -2075,7 +2068,7 @@ await describe('CoherentMeterValues', async () => { evseMaxPowerW: 22000, evseMeterValues: [], numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2107,7 +2100,7 @@ await describe('CoherentMeterValues', async () => { const connectorTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: MeterValueUnit.PERCENT, - } as unknown as SampledValueTemplate + } // groupUnderEvse=true + no evseMeterValues => getEvseIdByConnectorId returns 1, // getEvseStatus(1).MeterValues = undefined. resolveTemplates must fall back // to connector-level (guard: isNotEmptyArray(evseTemplates) narrows undefined @@ -2117,7 +2110,7 @@ await describe('CoherentMeterValues', async () => { evseMaxPowerW: 22000, groupUnderEvse: true, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2154,7 +2147,7 @@ await describe('CoherentMeterValues', async () => { const siblingTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.POWER_ACTIVE_IMPORT, unit: MeterValueUnit.WATT, - } as unknown as SampledValueTemplate + } const { connectorStatus, context, sessions } = buildContext({ currentType: CurrentType.AC, evseMaxPowerW: 22000, @@ -2162,7 +2155,7 @@ await describe('CoherentMeterValues', async () => { groupUnderEvse: true, numberOfPhases: 3, siblingConnectorMeterValues: [siblingTemplate], - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2203,7 +2196,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 7400, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2245,7 +2238,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 22000, numberOfPhases: 3, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2292,7 +2285,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.DC, evseMaxPowerW: 50000, numberOfPhases: 0, - voltageOut: 400, + voltageOut: Voltage.VOLTAGE_400, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2341,7 +2334,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 7400, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, @@ -2408,7 +2401,7 @@ await describe('CoherentMeterValues', async () => { currentType: CurrentType.AC, evseMaxPowerW: 7400, numberOfPhases: 1, - voltageOut: 230, + voltageOut: Voltage.VOLTAGE_230, }) const session = createSessionOrFail(context, { connectorId: 1, diff --git a/tests/charging-station/meter-values/StrategyDispatch.test.ts b/tests/charging-station/meter-values/StrategyDispatch.test.ts index 64b89e51..ba9da0e6 100644 --- a/tests/charging-station/meter-values/StrategyDispatch.test.ts +++ b/tests/charging-station/meter-values/StrategyDispatch.test.ts @@ -44,7 +44,7 @@ const energyTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', -} as unknown as SampledValueTemplate +} await describe('StrategyDispatch', async () => { let station: ChargingStation @@ -148,7 +148,7 @@ await describe('StrategyDispatch', async () => { { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: 'Percent', - } as unknown as SampledValueTemplate, + }, ] connectorStatus.transactionId = TEST_TRANSACTION_ID connectorStatus.transactionEnergyActiveImportRegisterValue = 0 @@ -230,7 +230,7 @@ await describe('StrategyDispatch', async () => { phase: MeterValuePhase.L3_N, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] connectorStatus.transactionId = TEST_TRANSACTION_ID connectorStatus.transactionEnergyActiveImportRegisterValue = 6000 connectorStatus.energyActiveImportRegisterValue = 6000 @@ -271,10 +271,14 @@ await describe('StrategyDispatch', async () => { const connectorStatus = station.getConnectorStatus(1) assert.ok(connectorStatus != null) const templates = connectorStatus.MeterValues - connectorStatus.MeterValues = ['sensor-a', 'sensor-b'].flatMap(vendorId => + connectorStatus.MeterValues = ['sensor-a', 'sensor-b'].flatMap((vendorId, index) => templates.map(template => ({ ...template, - customData: { vendorId }, + unitOfMeasure: { + customData: { vendorId }, + multiplier: index, + unit: MeterValueUnit.WATT_HOUR, + }, })) ) const meterValue = buildMeterValue( @@ -290,16 +294,21 @@ await describe('StrategyDispatch', async () => { assert.strictEqual( energySamples.length, 2, - 'phase suppression must preserve distinct customData register families' + 'phase suppression must preserve distinct native unit descriptor families' ) assert.deepStrictEqual( energySamples.map(sample => [ - (sample as { customData?: { vendorId?: string } }).customData?.vendorId, + ( + sample as { + unitOfMeasure?: { customData?: { vendorId?: string }; multiplier?: number } + } + ).unitOfMeasure?.customData?.vendorId, + (sample as { unitOfMeasure?: { multiplier?: number } }).unitOfMeasure?.multiplier, sample.phase, ]), [ - ['sensor-a', undefined], - ['sensor-b', undefined], + ['sensor-a', 0, undefined], + ['sensor-b', 1, undefined], ] ) }) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts index 1eeab0b4..766d2663 100644 --- a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts @@ -8,7 +8,9 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { ResetRequest } from '../../../../src/types/index.js' +import { OCPPError } from '../../../../src/exception/index.js' import { + ErrorType, GenericStatus, OCPP16IncomingRequestCommand, ResetType, @@ -72,6 +74,17 @@ await describe('OCPP16IncomingRequestService — Reset', async () => { assert.strictEqual(response.status, GenericStatus.Accepted) }) + await it('should reject an invalid reset type before scheduling a reset', () => { + const { testableService } = testContext + const station = ResetFixtures.createStandardStation(0) + const resetRequest = { type: 'Invalid' } as unknown as ResetRequest + + assert.throws( + () => testableService.handleRequestReset(station, resetRequest), + (error: unknown) => error instanceof OCPPError && error.code === ErrorType.FORMAT_VIOLATION + ) + }) + // @spec §5.13 — TC_024_CS: Hard reset with active transaction await it('should handle hard reset request with active transaction', () => { // Arrange diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts index 40813f0a..47c30c0c 100644 --- a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts @@ -20,7 +20,10 @@ import type { RequestParams, } from '../../../../src/types/index.js' -import { TransactionMeterValueDeliveryBarrier } from '../../../../src/charging-station/meter-values/index.js' +import { + type CoherentSession, + TransactionMeterValueDeliveryBarrier, +} from '../../../../src/charging-station/meter-values/index.js' import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js' import { OCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.js' import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js' @@ -29,10 +32,13 @@ import { CurrentType, ErrorType, OCPP16AuthorizationStatus, + OCPP16AvailabilityType, + OCPP16ChargePointStatus, OCPP16DiagnosticsStatus, OCPP16FirmwareStatus, OCPP16IncomingRequestCommand, OCPP16MessageTrigger, + OCPP16MeterValueContext, OCPP16MeterValueFormat, OCPP16MeterValueLocation, OCPP16MeterValueMeasurand, @@ -44,6 +50,7 @@ import { OCPP16VendorParametersKey, OCPPVersion, PublicKeyWithSignedMeterValueEnumType, + Voltage, } from '../../../../src/types/index.js' import { Constants, logger } from '../../../../src/utils/index.js' import { @@ -189,6 +196,81 @@ await describe('OCPP16IncomingRequestService — TriggerMessage', async () => { // Assert assert.strictEqual(response.status, OCPP16TriggerMessageStatus.ACCEPTED) }) + + await it('should send an OCPP 1.6 coherent snapshot for a MeterValues trigger', async () => { + // Arrange + const { incomingRequestService, station, testableService } = context + const transactionId = 100 + setupConnectorWithTransaction(station, 1, { transactionId }) + assert.ok(station.stationInfo != null) + station.stationInfo.coherentMeterValues = true + enableConnectorMeterValues(station, 1) + const session: CoherentSession = { + connectorId: 1, + currentType: CurrentType.AC, + numberOfPhases: 3, + profile: { + batteryCapacityWh: 40000, + chargingCurve: [ + { powerFraction: 1, socPercent: 0 }, + { powerFraction: 1, socPercent: 100 }, + ], + id: 'trigger-test', + initialSocPercentMax: 30, + initialSocPercentMin: 30, + maxPowerW: 11000, + weight: 1, + }, + rampUpDurationMs: 0, + sessionStartMs: Date.now(), + socPercent: 30, + transactionId, + voltageOutNominal: Voltage.VOLTAGE_230, + } + station.__injectCoherentSession(transactionId, session) + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + const energyBefore = connectorStatus.transactionEnergyActiveImportRegisterValue + const socBefore = session.socPercent + let sentRequest: OCPP16MeterValuesRequest | undefined + ;( + station.ocppRequestService as unknown as { + requestHandler: (...args: unknown[]) => Promise + } + ).requestHandler = (...args: unknown[]) => { + if (args[1] === OCPP16RequestCommand.METER_VALUES) { + sentRequest = args[2] as OCPP16MeterValuesRequest + } + return Promise.resolve({}) + } + const request: OCPP16TriggerMessageRequest = { + connectorId: 1, + requestedMessage: OCPP16MessageTrigger.MeterValues, + } + + // Act + const response = testableService.handleRequestTriggerMessage(station, request) + incomingRequestService.emit( + OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, + station, + request, + response + ) + await flushMicrotasks() + + // Assert + assert.strictEqual(response.status, OCPP16TriggerMessageStatus.ACCEPTED) + assert.ok(sentRequest != null) + assert.strictEqual(sentRequest.connectorId, 1) + assert.ok((sentRequest.meterValue[0]?.sampledValue.length ?? 0) > 0) + assert.strictEqual( + sentRequest.meterValue[0]?.sampledValue[0]?.context, + OCPP16MeterValueContext.TRIGGER + ) + assert.strictEqual(typeof sentRequest.meterValue[0]?.sampledValue[0]?.value, 'string') + assert.strictEqual(connectorStatus.transactionEnergyActiveImportRegisterValue, energyBefore) + assert.strictEqual(session.socPercent, socBefore) + }) }) await it('should keep active transaction interval state unchanged during MeterValues admission', () => { @@ -1114,7 +1196,7 @@ await describe('OCPP16IncomingRequestService — TriggerMessage', async () => { // Act const response = testableService.handleRequestTriggerMessage(station, { - requestedMessage: 'UnknownMessage' as OCPP16MessageTrigger, + requestedMessage: 'UnknownMessage' as unknown as OCPP16MessageTrigger, }) // Assert @@ -1236,6 +1318,53 @@ await describe('OCPP16IncomingRequestService — TriggerMessage', async () => { }) } + await it('should include connector zero with a derived valid status in a broadcast trigger', () => { + // Arrange + const stationStatus = station.getConnectorStatus(0) + const connectorOneStatus = station.getConnectorStatus(1) + const connectorTwoStatus = station.getConnectorStatus(2) + assert.ok(stationStatus != null) + assert.ok(connectorOneStatus != null) + assert.ok(connectorTwoStatus != null) + delete stationStatus.status + stationStatus.availability = OCPP16AvailabilityType.Operative + connectorOneStatus.status = OCPP16ChargePointStatus.Available + connectorTwoStatus.status = OCPP16ChargePointStatus.Unavailable + const request: OCPP16TriggerMessageRequest = { + requestedMessage: OCPP16MessageTrigger.StatusNotification, + } + + // Act + incomingRequestServiceForListener.emit( + OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, + station, + request, + { status: OCPP16TriggerMessageStatus.ACCEPTED } + ) + + // Assert + assert.deepStrictEqual( + requestHandlerMock.mock.calls.map(call => call.arguments.slice(1)), + [ + [ + OCPP16RequestCommand.STATUS_NOTIFICATION, + { connectorId: 0, status: OCPP16ChargePointStatus.Available }, + { triggerMessage: true }, + ], + [ + OCPP16RequestCommand.STATUS_NOTIFICATION, + { connectorId: 1, status: OCPP16ChargePointStatus.Available }, + { triggerMessage: true }, + ], + [ + OCPP16RequestCommand.STATUS_NOTIFICATION, + { connectorId: 2, status: OCPP16ChargePointStatus.Unavailable }, + { triggerMessage: true }, + ], + ] + ) + }) + await it('should handle requestHandler rejection gracefully', async () => { const rejectingMock = mock.fn(async () => Promise.reject(new Error('test error'))) const { station: rejectStation } = createMockChargingStation({ diff --git a/tests/charging-station/ocpp/1.6/OCPP16Integration-CoherentMeterValues.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Integration-CoherentMeterValues.test.ts index fb1a6762..86f993a6 100644 --- a/tests/charging-station/ocpp/1.6/OCPP16Integration-CoherentMeterValues.test.ts +++ b/tests/charging-station/ocpp/1.6/OCPP16Integration-CoherentMeterValues.test.ts @@ -81,18 +81,18 @@ const configureMeterValueTemplates = (station: ChargingStation): void => { const templates: SampledValueTemplate[] = [ { measurand: MeterValueMeasurand.STATE_OF_CHARGE, - unit: 'Percent', - } as unknown as SampledValueTemplate, - { measurand: MeterValueMeasurand.VOLTAGE, unit: 'V' } as unknown as SampledValueTemplate, + unit: OCPP16MeterValueUnit.PERCENT, + }, + { measurand: MeterValueMeasurand.VOLTAGE, unit: OCPP16MeterValueUnit.VOLT }, { measurand: MeterValueMeasurand.POWER_ACTIVE_IMPORT, - unit: 'W', - } as unknown as SampledValueTemplate, - { measurand: MeterValueMeasurand.CURRENT_IMPORT, unit: 'A' } as unknown as SampledValueTemplate, + unit: OCPP16MeterValueUnit.WATT, + }, + { measurand: MeterValueMeasurand.CURRENT_IMPORT, unit: OCPP16MeterValueUnit.AMP }, { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: OCPP16MeterValueUnit.WATT_HOUR, - } as unknown as SampledValueTemplate, + }, ] const connectorStatus = station.getConnectorStatus(CONNECTOR_ID) if (connectorStatus != null) { diff --git a/tests/charging-station/ocpp/1.6/OCPP16RequestBuilders.test.ts b/tests/charging-station/ocpp/1.6/OCPP16RequestBuilders.test.ts new file mode 100644 index 00000000..5c185629 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16RequestBuilders.test.ts @@ -0,0 +1,61 @@ +/** + * @file Tests for OCPP16RequestBuilders + * @description Verifies OCPP 1.6 version-specific pure builders + */ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { buildOCPP16SampledValue } from '../../../../src/charging-station/ocpp/1.6/OCPP16RequestBuilders.js' +import { BaseError } from '../../../../src/exception/index.js' +import { + OCPP16MeterValueMeasurand, + OCPP16MeterValueUnit, + type SampledValueTemplate, +} from '../../../../src/types/index.js' + +await describe('OCPP16RequestBuilders', async () => { + await describe('buildOCPP16SampledValue', async () => { + for (const unit of [OCPP16MeterValueUnit.TEMP_CELCIUS, OCPP16MeterValueUnit.TEMP_CELSIUS]) { + await it(`should preserve the schema-supported ${unit} temperature unit`, () => { + const sampledValue = buildOCPP16SampledValue({ unit }, 21) + + assert.strictEqual(sampledValue.unit, unit) + }) + } + + await it('should omit the OCPP 2.0-only default unit for OCPP 1.6 Frequency', () => { + const sampledValue = buildOCPP16SampledValue( + { measurand: OCPP16MeterValueMeasurand.FREQUENCY }, + 50 + ) + + assert.strictEqual(sampledValue.unit, undefined) + }) + + await it('should reject an OCPP 2.0-only unit descriptor', () => { + assert.throws( + () => + buildOCPP16SampledValue( + { + unitOfMeasure: { multiplier: 3, unit: OCPP16MeterValueUnit.WATT_HOUR }, + }, + 1 + ), + (error: unknown) => + error instanceof BaseError && error.message.includes('sampled value unitOfMeasure') + ) + }) + + for (const fieldName of ['context', 'location', 'measurand', 'phase', 'unit'] as const) { + await it(`should reject an invalid supplied ${fieldName}`, () => { + const template = { [fieldName]: 'Invalid' } as unknown as SampledValueTemplate + + assert.throws( + () => buildOCPP16SampledValue(template, 1), + (error: unknown) => + error instanceof BaseError && error.message.includes(`sampled value ${fieldName}`) + ) + }) + } + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils-SignedMeterValues.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils-SignedMeterValues.test.ts index cac239b7..13b6223e 100644 --- a/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils-SignedMeterValues.test.ts +++ b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils-SignedMeterValues.test.ts @@ -198,6 +198,33 @@ await describe('OCPP 1.6 — Signed MeterValues', async () => { assert.strictEqual(signedSamples[0].context, OCPP16MeterValueContext.TRANSACTION_BEGIN) }) + await it('should not sign when the configured signing method is invalid', () => { + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + connectorStatus.transactionId = 42 + upsertConfigurationKey(station, OCPP16VendorParametersKey.SampledDataSignReadings, 'true') + upsertConfigurationKey( + station, + OCPP16VendorParametersKey.SampledDataSignStartedReadings, + 'true' + ) + upsertConfigurationKey( + station, + `${OCPP16VendorParametersKey.MeterPublicKey}1`, + TEST_PUBLIC_KEY_HEX + ) + upsertConfigurationKey(station, OCPP16VendorParametersKey.SigningMethod, 'invalid') + + const meterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, 5000) + + assert.strictEqual( + meterValue.sampledValue.some( + sampledValue => sampledValue.format === OCPP16MeterValueFormat.SIGNED_DATA + ), + false + ) + }) + await it('should project only the ordinary DC Inlet begin sample while signing the raw Outlet reading', () => { assert.ok(station.stationInfo != null) station.stationInfo.conversionEfficiency = 0.8 diff --git a/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts index 9a7a1487..df8e34c1 100644 --- a/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts +++ b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts @@ -48,6 +48,7 @@ import { type OCPP16StopTransactionResponse, OCPP16SupportedFeatureProfiles, OCPP16VendorParametersKey, + OCPP20ReasonEnumType, OCPPVersion, type RequestParams, } from '../../../../src/types/index.js' @@ -729,6 +730,37 @@ await describe('OCPP16ServiceUtils — pure functions', async () => { ) } + for (const invalidReason of [ + 'InvalidReason', + OCPP20ReasonEnumType.EnergyLimitReached, + ] as const) { + await it(`should reject unsupported reason '${invalidReason}' before mutating transaction state`, async () => { + const requestHandler = mock.fn(() => Promise.resolve({})) + const { station } = createMockChargingStation({ + ocppRequestService: { requestHandler }, + ocppVersion: OCPPVersion.VERSION_16, + }) + setupConnectorWithTransaction(station, 1, { transactionId: 100 }) + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + + const requestOverrides = { + reason: invalidReason, + } as unknown as Partial + + await assert.rejects( + OCPP16ServiceUtils.stopTransactionOnConnector(station, 1, undefined, requestOverrides), + (error: unknown) => + error instanceof OCPPError && + error.code === ErrorType.FORMAT_VIOLATION && + error.message.includes('Invalid StopTransaction reason') + ) + + assert.notStrictEqual(connectorStatus.transactionEnding, true) + assert.strictEqual(requestHandler.mock.callCount(), 0) + }) + } + await it('should suppress periodic MeterValues while a transaction is ending', t => { t.mock.timers.enable({ apis: ['setInterval'] }) const requestHandler = mock.fn(() => Promise.resolve({})) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts index 4db42dad..92d9ace6 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts @@ -8,7 +8,6 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { ConnectorStatus, - EvseStatus, OCPP20FirmwareStatusNotificationRequest, OCPP20MeterValuesRequest, OCPP20TransactionEventRequest, @@ -43,6 +42,7 @@ import { OCPP20RequiredVariableName, OCPP20TransactionEventEnumType, OCPP20TriggerReasonEnumType, + OCPP20UnitEnumType, OCPPVersion, PublicKeyWithSignedMeterValueEnumType, ReasonCodeEnumType, @@ -83,9 +83,9 @@ function addTriggerMessageConnector ( connectorStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.energyActiveImportRegisterValue = energyValue evseStatus.connectors.set(connectorId, connectorStatus) return connectorStatus @@ -162,9 +162,9 @@ function createTriggerMessageStation (): { connectorStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.energyActiveImportRegisterValue = (evseId ?? 0) * 10 } return { mockStation, requestHandlerMock } @@ -608,9 +608,9 @@ await describe('F06 - TriggerMessage', async () => { { location: OCPP20LocationEnumType.Inlet, measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.transactionEnergyActiveImportRegisterValue = 150 connectorStatus.transactionEnergyActiveImportIntervalBaselines = { [baselineKey]: 60 } connectorStatus.transactionEnergyActiveImportIntervalCarry = { [baselineKey]: 0 } @@ -749,9 +749,9 @@ await describe('F06 - TriggerMessage', async () => { connectorStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.transactionId = `duplicate-response-${evseId.toString()}` connectorStatus.transactionStarted = true } @@ -1458,9 +1458,9 @@ await describe('F06 - TriggerMessage', async () => { connectorStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.transactionEnergyActiveImportRegisterValue = 0 connectorStatus.transactionEnergyActiveImportIntervalBaselines = { [baselineKey]: 0 } connectorStatus.transactionEnergyActiveImportIntervalCarry = { [baselineKey]: 0 } @@ -1556,14 +1556,14 @@ await describe('F06 - TriggerMessage', async () => { { fluctuationPercent: 0, measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, { fluctuationPercent: 0, measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, - unit: 'Wh', + unit: OCPP20UnitEnumType.WATT_HOUR, }, - ] as unknown as NonNullable + ] connectorStatus.transactionEnergyActiveImportRegisterValue = 150 // Pin the register read ahead of the sample window so the coalesced interval energy // reflects only the register delta, never wall-clock proration (deterministic on any host). @@ -1974,7 +1974,7 @@ await describe('F06 - TriggerMessage', async () => { function seedActiveTransaction (evseId: number, transactionId: string): void { const evseStatus = mockStation.getEvseStatus(evseId) if (evseStatus != null) { - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as EvseStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } setupConnectorWithTransaction(mockStation, evseId, { energyImport: 1234, @@ -2420,7 +2420,7 @@ await describe('F06 - TriggerMessage', async () => { assert.ok(evseStatus != null && connectorStatus != null) evseStatus.connectors.delete(2) evseStatus.connectors.set(1, connectorStatus) - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as EvseStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] connectorStatus.transactionStarted = true connectorStatus.transactionId = 'txn-evse-2-local-connector' connectorStatus.transactionEnergyActiveImportRegisterValue = 1234 @@ -2522,7 +2522,7 @@ await describe('F06 - TriggerMessage', async () => { }) const rejectEvseStatus = rejectStation.getEvseStatus(1) if (rejectEvseStatus != null) { - rejectEvseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as EvseStatus['MeterValues'] + rejectEvseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } setupConnectorWithTransaction(rejectStation, 1, { energyImport: 1234, diff --git a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts index 06423a27..12fadaaa 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts @@ -167,7 +167,7 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten }) await it('should reject SetVariables on an unknown component and confirm GetVariables returns UnknownComponent', () => { - const unknownComponent = { name: 'NonExistentComponent' as OCPP20ComponentName } + const unknownComponent = { name: 'NonExistentComponent' as unknown as OCPP20ComponentName } const variableName = 'SomeVariable' // Attempt to set a variable on a component that does not exist in the registry diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders-SignedMeterValues.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders-SignedMeterValues.test.ts index 79c078ac..d81a094d 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders-SignedMeterValues.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders-SignedMeterValues.test.ts @@ -36,13 +36,13 @@ const energyTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', -} as unknown as SampledValueTemplate +} const voltageTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.VOLTAGE, unit: 'V', value: '230', -} as unknown as SampledValueTemplate +} await describe('OCPP 2.0 Signed Meter Values', async () => { await describe('buildOCPP20SampledValue with signing config', async () => { diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders.test.ts index ae61f00c..8f9a4d47 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestBuilders.test.ts @@ -9,9 +9,19 @@ import assert from 'node:assert/strict' import { afterEach, describe, it } from 'node:test' -import type { StopTransactionReason } from '../../../../src/types/index.js' - -import { mapStopReasonToOCPP20 } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestBuilders.js' +import { + buildOCPP20SampledValue, + mapStopReasonToOCPP20, +} from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestBuilders.js' +import { BaseError } from '../../../../src/exception/index.js' +import { + OCPP16MeterValueMeasurand, + OCPP16StopTransactionReason, + OCPP20MeasurandEnumType, + OCPP20UnitEnumType, + PublicKeyWithSignedMeterValueEnumType, + type SampledValueTemplate, +} from '../../../../src/types/index.js' import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js' await describe('OCPP20RequestBuilders', async () => { @@ -19,9 +29,68 @@ await describe('OCPP20RequestBuilders', async () => { standardCleanup() }) + await describe('buildOCPP20SampledValue', async () => { + await it('should reject an OCPP 1.6-only measurand', () => { + assert.throws( + () => buildOCPP20SampledValue({ measurand: OCPP16MeterValueMeasurand.FAN_RPM }, 1), + (error: unknown) => + error instanceof BaseError && error.message.includes('sampled value measurand') + ) + }) + + await it('should reject an invalid measurand', () => { + const template = { measurand: 'Invalid' } as unknown as SampledValueTemplate + + assert.throws( + () => buildOCPP20SampledValue(template, 1), + (error: unknown) => + error instanceof BaseError && error.message.includes('sampled value measurand') + ) + }) + + await it('should preserve a custom unit string', () => { + const { sampledValue } = buildOCPP20SampledValue({ unit: 'custom-unit' }, 1) + + assert.deepStrictEqual(sampledValue.unitOfMeasure, { unit: 'custom-unit' }) + }) + + await it('should preserve physical quantity across standardized unit multipliers', () => { + const { sampledValue } = buildOCPP20SampledValue( + { + measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, + unit: OCPP20UnitEnumType.WATT_HOUR, + unitOfMeasure: { multiplier: 3, unit: OCPP20UnitEnumType.KILO_WATT_HOUR }, + }, + 2_500_000, + undefined, + undefined, + { + enabled: true, + meterSerialNumber: 'SIM-METER-001', + publicKeySentInTransaction: false, + publicKeyWithSignedMeterValue: PublicKeyWithSignedMeterValueEnumType.Never, + transactionId: 'tx-1', + } + ) + + assert.strictEqual(sampledValue.value, 2.5) + assert.deepStrictEqual(sampledValue.unitOfMeasure, { + multiplier: 3, + unit: OCPP20UnitEnumType.KILO_WATT_HOUR, + }) + assert.strictEqual(sampledValue.value * 1_000 * 10 ** 3, 2_500_000) + assert.match( + Buffer.from(sampledValue.signedMeterValue?.signedMeterData ?? '', 'base64').toString( + 'utf8' + ), + /"RV":2500/u + ) + }) + }) + await describe('mapStopReasonToOCPP20', async () => { await it('should map Other to Other/AbnormalCondition', () => { - const result = mapStopReasonToOCPP20('Other' as StopTransactionReason) + const result = mapStopReasonToOCPP20(OCPP16StopTransactionReason.OTHER) assert.strictEqual(result.stoppedReason, 'Other') assert.strictEqual(result.triggerReason, 'AbnormalCondition') @@ -35,7 +104,7 @@ await describe('OCPP20RequestBuilders', async () => { }) await it('should map Remote to Remote/RemoteStop', () => { - const result = mapStopReasonToOCPP20('Remote' as StopTransactionReason) + const result = mapStopReasonToOCPP20(OCPP16StopTransactionReason.REMOTE) assert.strictEqual(result.stoppedReason, 'Remote') assert.strictEqual(result.triggerReason, 'RemoteStop') diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-AlignedMeterValues.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-AlignedMeterValues.test.ts index 8c01efcc..59602f37 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-AlignedMeterValues.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-AlignedMeterValues.test.ts @@ -214,7 +214,7 @@ function createAlignedStation ( for (const evseId of evseIds) { const evseStatus = mockStation.getEvseStatus(evseId) if (evseStatus != null) { - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as EvseStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } } const seedRegister = (connectorId: number, value: number): void => { @@ -359,7 +359,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', evseStatus.MeterValues = [] connector1.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, { @@ -368,7 +368,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '230', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -409,14 +409,14 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { customData: { vendorId: 'sensor-b' }, measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -460,7 +460,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '210', }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { fluctuationPercent: 0, @@ -468,7 +468,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '240', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, ALIGNED_MEASURANDS_KEY, OCPP20MeasurandEnumType.VOLTAGE) @@ -516,7 +516,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(connector2 != null) stationEvse.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_REACTIVE_IMPORT_REGISTER, unit: 'varh' }, - ] as unknown as NonNullable + ] evseStatus.MeterValues = [] connector1.MeterValues = [ { @@ -525,7 +525,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '1000', }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { fluctuationPercent: 0, @@ -533,7 +533,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '2000', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -582,7 +582,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit, value, }, - ] as unknown as NonNullable + ] } for (const evseId of [1, 2]) { const evseStatus = mockStation.getEvseStatus(evseId) @@ -628,7 +628,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as NonNullable + ] evse1.MeterValues = [ { fluctuationPercent: 0, @@ -644,7 +644,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '9000', }, - ] as unknown as NonNullable + ] evse2.MeterValues = [ { fluctuationPercent: 0, @@ -653,7 +653,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '2000', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -727,12 +727,12 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: outletValue.toString(), }, ] - ) as unknown as NonNullable + ) stationEvse.MeterValues = sourcePairs.map(([measurand, unit]) => ({ location: OCPP20LocationEnumType.Outlet, measurand, unit, - })) as unknown as NonNullable + })) upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -804,12 +804,12 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '400', }, ] - physicalEvse.MeterValues = templates as unknown as NonNullable + physicalEvse.MeterValues = templates stationEvse.MeterValues = templates.map(template => ({ ...template, location: OCPP20LocationEnumType.Inlet, value: undefined, - })) as unknown as NonNullable + })) upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -865,7 +865,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'kW', value: '1', }, - ] as unknown as ConnectorStatus['MeterValues'] + ] const stationMeter = mockStation.getEvseStatus(0) assert.ok(stationMeter != null) stationMeter.MeterValues = [ @@ -875,7 +875,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '0', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -913,7 +913,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Percent', value: '50', }, - ] as unknown as NonNullable + ] stationMeter.MeterValues = [] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-soc' }) upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') @@ -956,7 +956,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit, value, }, - ] as unknown as NonNullable + ] } upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') @@ -999,7 +999,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit, value: value.toString(), }, - ] as unknown as NonNullable + ] } upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') @@ -1107,7 +1107,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '1000', }, - ] as unknown as NonNullable + ] } } setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-1' }) @@ -1199,7 +1199,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -1213,7 +1213,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-suppressed' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -1280,7 +1280,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-independent-cadences' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -1371,7 +1371,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -1379,7 +1379,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] const transactionId = `tx-station-cadence-${String(meteringPerTransaction)}` const baselineTimestamp = Date.now() setupConnectorWithTransaction(mockStation, 1, { transactionId }) @@ -1456,7 +1456,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '230', }, - ] as unknown as EvseStatus['MeterValues'] + ] connectorStatus.energyActiveImportRegisterValue = 115 connectorStatus.energyActiveImportIntervalBaselines = { [`station:${ALIGNED_MEASURANDS_KEY}`]: 100, @@ -1496,7 +1496,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] const activeAt = new Date(60_000) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-active-idle-baseline' }) connectorStatus.energyActiveImportRegisterValue = 40 @@ -1553,7 +1553,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.energyActiveImportIntervalBaseline = 80 connector1.energyActiveImportRegisterValue = 40 connector2.energyActiveImportRegisterValue = 60 @@ -1609,7 +1609,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] connector1.energyActiveImportRegisterValue = 10 connector2.energyActiveImportRegisterValue = 30 connector1.energyActiveImportIntervalBaselines = { @@ -1663,7 +1663,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: OCPP20UnitEnumType.WATT_HOUR, value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] const stationBaselineKey = `station:${ALIGNED_MEASURANDS_KEY}` connectorStatus.energyActiveImportRegisterValue = 10 connectorStatus.energyActiveImportIntervalBaselines = { @@ -1754,7 +1754,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-shared-evse-restore' }) const connector1 = mockStation.getConnectorStatus(1, 1) const connector2 = mockStation.getConnectorStatus(2, 1) @@ -1991,7 +1991,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] connectorStatus.energyActiveImportRegisterValue = 40 connectorStatus.transactionEnergyActiveImportRegisterValue = 0 connectorStatus.energyActiveImportIntervalBaselines = { @@ -2104,7 +2104,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(stationEvseStatus != null) assert.ok(evseStatus != null) assert.ok(connectorStatus != null) - stationEvseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as EvseStatus['MeterValues'] + stationEvseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -2112,7 +2112,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '0', }, - ] as unknown as EvseStatus['MeterValues'] + ] connectorStatus.energyActiveImportRegisterValue = 40 connectorStatus.transactionEnergyActiveImportRegisterValue = 0 connectorStatus.energyActiveImportIntervalBaselines = { @@ -2192,7 +2192,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-between-slots' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -2738,7 +2738,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_REACTIVE_IMPORT_INTERVAL, unit: 'varh', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -2766,7 +2766,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '5', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-coalesced' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -2857,7 +2857,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -2865,7 +2865,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.energyActiveImportRegisterLastUpdatedAt = new Date(0) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-meter-values-retry' }) let failed = false @@ -3167,6 +3167,26 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.strictEqual(energySample.value, 1234) }) + await it('should not sign active aligned samples when the configured signing method is invalid', async () => { + const { mockStation, requestHandlerMock } = alignedStation + upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') + upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') + upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') + upsertConfigurationKey(mockStation, SIGN_READINGS_KEY, 'true') + upsertConfigurationKey(mockStation, SIGN_UPDATED_READINGS_KEY, 'true') + upsertConfigurationKey(mockStation, FISCAL_PUBLIC_KEY, TEST_PUBLIC_KEY_HEX) + upsertConfigurationKey(mockStation, FISCAL_SIGNING_METHOD, 'invalid') + setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-invalid-method' }) + + await OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) + + const samples = sentTransactionEvents(requestHandlerMock).flatMap(transactionEvent => + (transactionEvent.meterValue ?? []).flatMap(meterValue => meterValue.sampledValue) + ) + assert.ok(samples.length > 0) + assert.ok(samples.every(sample => sample.signedMeterValue == null)) + }) + await it('should not sign active aligned samples when SignUpdatedReadings is disabled', async () => { const { mockStation, requestHandlerMock } = alignedStation upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') @@ -3257,7 +3277,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }) - ) as unknown as NonNullable + ) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-coherent' }) if (mockStation.stationInfo != null) mockStation.stationInfo.meteringPerTransaction = true const coherentConnectorStatus = mockStation.getConnectorStatus(1) @@ -3626,7 +3646,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '90') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -3701,7 +3721,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '1000', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -3732,7 +3752,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', mock.method(mockStation, 'getConnectorMaximumAvailablePower', () => 3600) evseStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -3790,7 +3810,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '230', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -3825,7 +3845,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '230', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey(mockStation, SEND_DURING_IDLE_KEY, 'false') @@ -3887,7 +3907,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '1000', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') setupConnectorWithTransaction(mockStation, 1, { @@ -3925,7 +3945,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '1000', }, - ] as unknown as NonNullable + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -4709,7 +4729,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] stationConnectorStatus.energyActiveImportRegisterValue = 7 void OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) @@ -4761,7 +4781,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -4770,7 +4790,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '800', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: `tx-station-power-${location}`, }) @@ -4814,7 +4834,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -4823,7 +4843,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '800', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-station-outlet-power', }) @@ -4880,7 +4900,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -4889,7 +4909,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '800', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-station-energy' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -4950,7 +4970,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', fluctuationPercent: 0, location: OCPP20LocationEnumType.Inlet, measurand, - })) as NonNullable + })) sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -5031,7 +5051,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', location: OCPP20LocationEnumType.Outlet, measurand, phase: MeterValuePhase.L1_N, - })) as NonNullable + })) sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -5075,7 +5095,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(sourceEvse != null) stationEvse.MeterValues = [ { measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W' }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [ MeterValuePhase.L1_N, MeterValuePhase.L2_N, @@ -5086,7 +5106,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'W', value: '1000', - })) as unknown as EvseStatus['MeterValues'] + })) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-phased-power' }) void OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) @@ -5118,7 +5138,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '9000', }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [ { fluctuationPercent: 0, @@ -5127,7 +5147,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '1000', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-partial-phase' }) void OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) @@ -5160,7 +5180,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '1000', }, - ] as unknown as EvseStatus['MeterValues'] + ] void OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) @@ -5187,8 +5207,8 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(stationEvse != null) assert.ok(sourceEvse != null) stationEvse.MeterValues = [ - { measurand: OCPP20MeasurandEnumType.CURRENT_IMPORT, unit: 'A' }, - ] as unknown as EvseStatus['MeterValues'] + { measurand: OCPP20MeasurandEnumType.CURRENT_IMPORT, unit: OCPP20UnitEnumType.AMP }, + ] sourceEvse.MeterValues = [ MeterValuePhase.L1_N, MeterValuePhase.L2_N, @@ -5199,7 +5219,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'A', value: '5', - })) as unknown as EvseStatus['MeterValues'] + })) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-current' }) void OCPP20ServiceUtils.emitClockAlignedMeterValues(mockStation) @@ -5222,8 +5242,8 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', const stationEvse = mockStation.getEvseStatus(0) assert.ok(stationEvse != null) stationEvse.MeterValues = [ - { measurand: OCPP20MeasurandEnumType.CURRENT_IMPORT, unit: 'A' }, - ] as unknown as EvseStatus['MeterValues'] + { measurand: OCPP20MeasurandEnumType.CURRENT_IMPORT, unit: OCPP20UnitEnumType.AMP }, + ] const phaseValues = [ [MeterValuePhase.L1_N, 10], [MeterValuePhase.L2_N, 20], @@ -5241,7 +5261,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'A', value: value.toString(), }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, evseId, { transactionId: `tx-cross-evse-current-${evseId.toString()}`, }) @@ -5284,7 +5304,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', ] as const stationEvse.MeterValues = measurands.map(measurand => ({ measurand, - })) as EvseStatus['MeterValues'] + })) const phaseValues = [ [MeterValuePhase.L1, 10], [MeterValuePhase.L1_N, 20], @@ -5299,7 +5319,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: measurand === OCPP20MeasurandEnumType.CURRENT_IMPORT ? 'A' : 'W', value: value.toString(), })) - ) as unknown as EvseStatus['MeterValues'] + ) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-phase-aliases' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -5347,7 +5367,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(phasedEvse != null) stationEvse.MeterValues = [ { measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W' }, - ] as unknown as EvseStatus['MeterValues'] + ] aggregateEvse.MeterValues = [ { fluctuationPercent: 0, @@ -5355,7 +5375,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '3000', }, - ] as unknown as EvseStatus['MeterValues'] + ] phasedEvse.MeterValues = [ MeterValuePhase.L1_N, MeterValuePhase.L2_N, @@ -5366,7 +5386,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'W', value: '1000', - })) as unknown as EvseStatus['MeterValues'] + })) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-aggregate-power' }) setupConnectorWithTransaction(mockStation, 2, { transactionId: 'tx-phased-power' }) @@ -5406,7 +5426,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '2000', }, - ] as unknown as EvseStatus['MeterValues'] + ] } setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-custom-data' }) @@ -5451,7 +5471,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as EvseStatus['MeterValues'] + ] sourceEvse.MeterValues = [] connector1.MeterValues = [ { @@ -5461,7 +5481,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'W', value: '1000', }, - ] as unknown as ConnectorStatus['MeterValues'] + ] connector2.MeterValues = [ { customData: (() => { @@ -5545,7 +5565,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }) - ) as unknown as EvseStatus['MeterValues'] + ) connectorStatus.energyActiveImportRegisterValue = 800 upsertConfigurationKey( mockStation, @@ -5597,7 +5617,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '800', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-main-energy' }) upsertConfigurationKey( mockStation, @@ -5664,7 +5684,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '230', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, buildConfigKey(OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands), @@ -5768,7 +5788,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', assert.ok(evseStatus != null) evseStatus.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: '00000000-0000-4000-8000-000000000123', }) @@ -5815,7 +5835,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-shared-energy-1' }) setupConnectorWithTransaction(mockStation, 2, { transactionId: 'tx-shared-energy-2' }) const connector1 = mockStation.getConnectorStatus(1, 1) @@ -5883,7 +5903,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -5937,7 +5957,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -5981,7 +6001,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6086,7 +6106,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6175,7 +6195,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '60', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6356,7 +6376,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6422,7 +6442,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6510,7 +6530,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6602,7 +6622,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'Wh', value: '90', - })) as unknown as EvseStatus['MeterValues'] + })) upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6652,7 +6672,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6699,7 +6719,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -6712,7 +6732,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6840,7 +6860,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -6929,7 +6949,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -7026,8 +7046,8 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', } stationEvse.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh' }, - ] as unknown as EvseStatus['MeterValues'] - evseStatus.MeterValues = [intervalTemplate] as unknown as EvseStatus['MeterValues'] + ] + evseStatus.MeterValues = [intervalTemplate] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey( mockStation, @@ -7088,7 +7108,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -7157,7 +7177,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, ALIGNED_MEASURANDS_KEY, @@ -7214,7 +7234,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', location: OCPP20LocationEnumType.Inlet, measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', - })) as unknown as EvseStatus['MeterValues'] + })) evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -7230,7 +7250,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', })), - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -7297,7 +7317,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -7372,7 +7392,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '10', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -7555,7 +7575,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as EvseStatus['MeterValues'] + ] evseStatus.MeterValues = [ { fluctuationPercent: 0, @@ -7574,7 +7594,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT, unit: 'W', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey(mockStation, ALIGNED_DATA_INTERVAL_KEY, '60') upsertConfigurationKey(mockStation, ALIGNED_ENABLED_KEY, 'true') upsertConfigurationKey( @@ -7661,7 +7681,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] upsertConfigurationKey( mockStation, TX_UPDATED_MEASURANDS_KEY, @@ -7804,10 +7824,10 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'varh', value: '1000', }, - ] as unknown as EvseStatus['MeterValues'] + ] stationEvse.MeterValues = [ { measurand: OCPP20MeasurandEnumType.ENERGY_REACTIVE_IMPORT_REGISTER, unit: 'varh' }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-shared-register-1' }) setupConnectorWithTransaction(mockStation, 2, { transactionId: 'tx-shared-register-2' }) @@ -7847,7 +7867,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -7887,7 +7907,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '80', }, - ] as unknown as EvseStatus['MeterValues'] + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-dc-inlet-interval' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -7948,7 +7968,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', }, { measurand: OCPP20MeasurandEnumType.VOLTAGE }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -7988,7 +8008,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Hz', value: '50', }, - ] as unknown as NonNullable + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -8077,7 +8097,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, phase, unit: 'Wh', - })) as unknown as EvseStatus['MeterValues'], + })), timestamp: new Date(60_000), }, 60_000, @@ -8112,7 +8132,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'Wh', value: '90', - })) as unknown as EvseStatus['MeterValues'] + })) const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -8158,7 +8178,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase, unit: 'Wh', value: '90', - })) as unknown as EvseStatus['MeterValues'] + })) setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-aligned-phase-interval' }) const connectorStatus = mockStation.getConnectorStatus(1, 1) assert.ok(connectorStatus != null) @@ -8231,7 +8251,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', }, - ] as unknown as NonNullable + ] setupConnectorWithTransaction(mockStation, 1, { transactionId: 'tx-phase-only' }) const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, @@ -8367,7 +8387,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '1000', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { fluctuationPercent: 0, @@ -8376,7 +8396,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '2000', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] setupConnectorWithTransaction(mockStation, 2, { transactionId: 'tx-2' }) const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, @@ -8417,7 +8437,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '100', }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { customData: { vendorId: 'connector-2' }, @@ -8426,7 +8446,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'Wh', value: '200', }, - ] as unknown as NonNullable + ] setupConnectorWithTransaction(mockStation, 2, { transactionId: 'tx-2' }) upsertConfigurationKey( mockStation, @@ -8521,7 +8541,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '211', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] connector2.MeterValues = [ { fluctuationPercent: 0, @@ -8543,7 +8563,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '240', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -8902,7 +8922,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase: MeterValuePhase.L2_N, unit: 'V', }, - ] as unknown as NonNullable + ] const invalidSinglePhaseMeterValue = buildClockAlignedConnectorMeterValue( mockStation, { connectorId: 1, evseId: 1 }, @@ -8944,7 +8964,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', phase: MeterValuePhase.N, unit: 'V', }, - ] as unknown as NonNullable + ] const dcMeterValue = buildClockAlignedConnectorMeterValue( mockStation, { connectorId: 1, evseId: 1 }, @@ -8975,7 +8995,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', value: '230', }, { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh' }, - ] as unknown as NonNullable + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands @@ -9066,6 +9086,63 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', false ) }) + + await it('should preserve custom unit metadata and physical values on automatic voltage phases', () => { + // Arrange + const { mockStation } = createAlignedStation({ connectorsCount: 1, evsesCount: 1 }) + mock.method(mockStation, 'getNumberOfPhases', () => 3) + assert.ok(mockStation.stationInfo != null) + mockStation.stationInfo.currentOutType = CurrentType.AC + mockStation.stationInfo.mainVoltageMeterValues = false + const evseStatus = mockStation.getEvseStatus(1) + assert.ok(evseStatus != null) + evseStatus.MeterValues = [ + { + fluctuationPercent: 0, + measurand: OCPP20MeasurandEnumType.VOLTAGE, + unit: 'custom-voltage', + unitOfMeasure: { + customData: { vendorId: 'custom-meter' }, + multiplier: 1, + unit: 'custom-voltage', + }, + value: '23', + }, + ] + const measurandsKey = buildConfigKey( + OCPP20ComponentName.AlignedDataCtrlr, + OCPP20RequiredVariableName.Measurands + ) + upsertConfigurationKey(mockStation, measurandsKey, OCPP20MeasurandEnumType.VOLTAGE) + + // Act + const meterValue = buildClockAlignedConnectorMeterValue( + mockStation, + { connectorId: 1, evseId: 1 }, + 60_000, + measurandsKey, + OCPP20ReadingContextEnumType.SAMPLE_CLOCK + ) + + // Assert + assert.deepStrictEqual( + meterValue.sampledValue.map(sample => ({ + phase: sample.phase, + physicalValue: sample.value * 10, + unitOfMeasure: sample.unitOfMeasure, + })), + [MeterValuePhase.L1_N, MeterValuePhase.L2_N, MeterValuePhase.L3_N].map(phase => ({ + phase, + physicalValue: 23, + unitOfMeasure: { + customData: { vendorId: 'custom-meter' }, + multiplier: 1, + unit: 'custom-voltage', + }, + })) + ) + }) + await it('should not suppress automatic voltage phases across customData identities', () => { const { mockStation } = createAlignedStation({ connectorsCount: 1, evsesCount: 1 }) mock.method(mockStation, 'getNumberOfPhases', () => 3) @@ -9091,7 +9168,7 @@ await describe('J01 - Autonomous clock-aligned MeterValues (#2011 Category 2F)', unit: 'V', value: '231', }, - ] as unknown as NonNullable + ] const measurandsKey = buildConfigKey( OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts index 24acb93a..217eba5f 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts @@ -14,7 +14,7 @@ import assert from 'node:assert/strict' import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { CoherentSession } from '../../../../src/charging-station/meter-values/index.js' -import type { ConnectorStatus, EmptyObject, EvseStatus } from '../../../../src/types/index.js' +import type { ConnectorStatus, EmptyObject } from '../../../../src/types/index.js' import { ChargingStation } from '../../../../src/charging-station/ChargingStation.js' import { @@ -50,6 +50,7 @@ import { ConnectorStatusEnum, CurrentType, ErrorType, + isOCPP20MeterValue, OCPP20ChargingStateEnumType, OCPP20ComponentName, OCPP20IdTokenEnumType, @@ -70,7 +71,6 @@ import { OCPPVersion, PublicKeyWithSignedMeterValueEnumType, type RequestParams, - type SampledValueTemplate, SigningMethodEnumType, Voltage, } from '../../../../src/types/index.js' @@ -747,7 +747,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] addConfigurationKey( station, intervalBaselineKey, @@ -3091,7 +3091,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { const transactionId = generateUUID() let online = false const attemptedSequenceNumbers: number[] = [] - const malformedTriggerReason = 'Bogus' as OCPP20TriggerReasonEnumType + const malformedTriggerReason = 'Bogus' as unknown as OCPP20TriggerReasonEnumType let successorPayload: OCPP20TransactionEventRequest | undefined const malformedError = new OCPPError( ErrorType.FORMAT_VIOLATION, @@ -5622,7 +5622,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL, unit: 'Wh', }, - ] as unknown as EvseStatus['MeterValues'] + ] addConfigurationKey( station, `${OCPP20ComponentName.OCPPCommCtrlr}.${OCPP20RequiredVariableName.MessageAttempts}.TransactionEvent`, @@ -5689,7 +5689,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', - } as unknown as SampledValueTemplate, + }, 20, undefined, undefined, @@ -6479,7 +6479,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', - } as unknown as SampledValueTemplate, + }, 20, undefined, undefined, @@ -6753,7 +6753,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { measurand: OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', - } as unknown as SampledValueTemplate, + }, 2, undefined, undefined, @@ -7245,6 +7245,74 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { assert.deepStrictEqual(observedIntervals, [1000, 10_000]) }) + await it('should send a periodic event without a MeterValue for an empty selection', async t => { + // Arrange + t.mock.timers.enable({ apis: ['setInterval'] }) + const transactionId = generateUUID() + setupConnectorWithTransaction(mockStation, 1, { transactionId }) + assert.ok(mockStation.stationInfo != null) + mockStation.stationInfo.coherentMeterValues = true + const session: CoherentSession = { + connectorId: 1, + currentType: CurrentType.AC, + numberOfPhases: 3, + profile: { + batteryCapacityWh: 40000, + chargingCurve: [ + { powerFraction: 1, socPercent: 0 }, + { powerFraction: 1, socPercent: 100 }, + ], + id: 'empty-selection-test', + initialSocPercentMax: 30, + initialSocPercentMin: 30, + maxPowerW: 11000, + weight: 1, + }, + rampUpDurationMs: 0, + sessionStartMs: Date.now(), + socPercent: 30, + transactionId, + voltageOutNominal: Voltage.VOLTAGE_230, + } + mockStation.__injectCoherentSession(transactionId, session) + addConfigurationKey( + mockStation, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + OCPP20RequiredVariableName.TxUpdatedMeasurands + ), + '', + undefined, + { overwrite: true } + ) + + // Act + const meterValue = OCPP20ServiceUtils.buildTransactionMeterValue( + mockStation, + 1, + 1, + transactionId, + 1000, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + OCPP20RequiredVariableName.TxUpdatedMeasurands + ) + ) + assert.deepStrictEqual(meterValue.sampledValue, []) + + OCPP20ServiceUtils.startUpdatedMeterValues(mockStation, 1, 1000, 1) + t.mock.timers.tick(1000) + for (let index = 0; index < 10; index++) await flushMicrotasks() + + // Assert + assert.strictEqual(sentRequests.length, 1) + assert.strictEqual( + sentRequests[0].payload.triggerReason, + OCPP20TriggerReasonEnumType.MeterValuePeriodic + ) + assert.strictEqual(sentRequests[0].payload.meterValue, undefined) + }) + await it('should not start timer when interval is zero', () => { const connectorId = 1 @@ -7681,7 +7749,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { mockTracking.station.getEvseIdByConnectorId(connectorId) ?? 1 ) if (evseStatus != null) { - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as ConnectorStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } addConfigurationKey( @@ -11126,7 +11194,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // Arrange const evseStatus = station.getEvseStatus(1) if (evseStatus != null) { - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as ConnectorStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } const transactionId = generateUUID() @@ -11181,7 +11249,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { unit: 'Wh', value: '1000', }, - ] as unknown as ConnectorStatus['MeterValues'] + ] addConfigurationKey( station, buildConfigKey( @@ -11237,7 +11305,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { unit: 'Wh', value: '1000000', }, - ] as unknown as ConnectorStatus['MeterValues'] + ] addConfigurationKey( station, buildConfigKey( @@ -11284,7 +11352,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 60_000, txUpdatedMeasurandsKey, OCPP20ReadingContextEnumType.SAMPLE_PERIODIC - ) as OCPP20MeterValue + ) + assert.ok(isOCPP20MeterValue(meterValue)) const expectedEnergyDelta = Number( ( (station.getConnectorMaximumAvailablePower(connectorId) * sampleInterval) / @@ -11372,7 +11441,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // Arrange const evseStatus = mockTracking.station.getEvseStatus(1) if (evseStatus != null) { - evseStatus.MeterValues = [{ unit: 'Wh' }] as unknown as ConnectorStatus['MeterValues'] + evseStatus.MeterValues = [{ unit: OCPP20UnitEnumType.WATT_HOUR }] } const transactionId = generateUUID() @@ -11427,7 +11496,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { unit: 'Wh', value: '1000', }, - ] as unknown as ConnectorStatus['MeterValues'] + ] const transactionId = generateUUID() setupConnectorWithTransaction(station, 1, { transactionId }) connectorStatus.transactionEnergyActiveImportRegisterValue = 1000 diff --git a/tests/charging-station/ocpp/OCPPIncomingRequestService-StationState.test.ts b/tests/charging-station/ocpp/OCPPIncomingRequestService-StationState.test.ts index 486e3ed6..b83617d2 100644 --- a/tests/charging-station/ocpp/OCPPIncomingRequestService-StationState.test.ts +++ b/tests/charging-station/ocpp/OCPPIncomingRequestService-StationState.test.ts @@ -126,10 +126,10 @@ await describe('OCPPIncomingRequestService — per-station state plumbing', asyn await it('should bind a nested post-restart CALL to the fresh station state', async () => { const oldState = plumbing.getOrCreateStationState(stationA) - const outerCommand = 'OuterInheritedCall' as Parameters< + const outerCommand = 'OuterInheritedCall' as unknown as Parameters< OCPP16IncomingRequestService['incomingRequestHandler'] >[2] - const nestedCommand = 'NestedRestartedCall' as Parameters< + const nestedCommand = 'NestedRestartedCall' as unknown as Parameters< OCPP16IncomingRequestService['incomingRequestHandler'] >[2] let freshState: OCPP16StationStateShape | undefined diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-MeterValues.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-MeterValues.test.ts index 05c258ce..30a8964d 100644 --- a/tests/charging-station/ocpp/OCPPServiceUtils-MeterValues.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceUtils-MeterValues.test.ts @@ -19,17 +19,20 @@ import type { ChargingStation } from '../../../src/charging-station/index.js' import { addConfigurationKey, buildConfigKey } from '../../../src/charging-station/index.js' import { + assertMeterValueBuilderResult, buildClockAlignedConnectorMeterValue, buildMeterValue, } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js' import { CurrentType, + type MeterValue, MeterValueContext, MeterValueLocation, MeterValueMeasurand, MeterValuePhase, MeterValueUnit, OCPP20ComponentName, + OCPP20LocationEnumType, OCPP20OptionalVariableName, OCPP20RequiredVariableName, OCPPVersion, @@ -49,31 +52,31 @@ const energyTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: 'Wh', value: '0', -} as unknown as SampledValueTemplate +} const socTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.STATE_OF_CHARGE, unit: 'Percent', value: '75', -} as unknown as SampledValueTemplate +} const voltageTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.VOLTAGE, unit: 'V', value: '230', -} as unknown as SampledValueTemplate +} const powerTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.POWER_ACTIVE_IMPORT, unit: 'W', value: '11000', -} as unknown as SampledValueTemplate +} const currentTemplate: SampledValueTemplate = { measurand: MeterValueMeasurand.CURRENT_IMPORT, unit: 'A', value: '16', -} as unknown as SampledValueTemplate +} await describe('buildMeterValue', async () => { let station: ChargingStation @@ -82,6 +85,21 @@ await describe('buildMeterValue', async () => { standardCleanup() }) + await it('should allow empty internal results but reject mixed version samples', () => { + const emptyMeterValue: MeterValue = { sampledValue: [], timestamp: new Date() } + assert.doesNotThrow(() => { + assertMeterValueBuilderResult(emptyMeterValue, OCPPVersion.VERSION_201) + }) + + const mixedMeterValue = { + sampledValue: [{ value: '1' }, { value: 1 }], + timestamp: new Date(), + } as unknown as MeterValue + assert.throws(() => { + assertMeterValueBuilderResult(mixedMeterValue, OCPPVersion.VERSION_201) + }, /do not match OCPP 2\.0\.1/u) + }) + await describe('OCPP 1.6', async () => { beforeEach(() => { const { station: s } = createMockChargingStation({ @@ -120,7 +138,7 @@ await describe('buildMeterValue', async () => { measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, unit: MeterValueUnit.WATT_HOUR, }, - ] as unknown as SampledValueTemplate[] + ] const meterValue = buildMeterValue(station, TEST_TRANSACTION_ID, 0) @@ -233,7 +251,7 @@ await describe('buildMeterValue', async () => { unit: 'Wh', value: '1000', }, - ] as unknown as SampledValueTemplate[] + ] buildMeterValue(testStation, TEST_TRANSACTION_ID_STRING, 3_600_000) @@ -261,7 +279,7 @@ await describe('buildMeterValue', async () => { unit: MeterValueUnit.WATT_HOUR, value: '0', }, - ] as unknown as SampledValueTemplate[] + ] addConfigurationKey( station, buildConfigKey(OCPP20ComponentName.AlignedDataCtrlr, OCPP20RequiredVariableName.Measurands), @@ -290,6 +308,92 @@ await describe('buildMeterValue', async () => { assert.deepStrictEqual(values, [[125], [0]]) }) + await it('should preserve physical energy when consuming a multiplied snapshot baseline', () => { + const template: SampledValueTemplate = { + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + unit: MeterValueUnit.WATT_HOUR, + unitOfMeasure: { multiplier: 1, unit: MeterValueUnit.WATT_HOUR }, + } + + const meterValue = buildClockAlignedConnectorMeterValue( + station, + { + connectorId: 1, + evseId: 1, + sampledValueBaseline: [ + { + location: OCPP20LocationEnumType.Outlet, + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + unitOfMeasure: { multiplier: -1, unit: MeterValueUnit.KILO_WATT_HOUR }, + value: 2, + }, + ], + sampledValueTemplates: [template], + transactionId: TEST_TRANSACTION_ID_STRING, + }, + 60_000, + undefined, + MeterValueContext.SAMPLE_CLOCK + ) + + assert.strictEqual(meterValue.sampledValue[0]?.value, 20) + assert.deepStrictEqual(meterValue.sampledValue[0]?.unitOfMeasure, { + multiplier: 1, + unit: MeterValueUnit.WATT_HOUR, + }) + }) + + await it('should preserve native unit descriptor families during snapshot phase suppression', () => { + addConfigurationKey( + station, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + OCPP20OptionalVariableName.RegisterValuesWithoutPhases + ), + 'true', + undefined, + { overwrite: true } + ) + const templates = ['sensor-a', 'sensor-b'].map((vendorId, index) => ({ + measurand: MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER, + phase: index === 0 ? MeterValuePhase.L1_N : MeterValuePhase.L2_N, + unit: MeterValueUnit.WATT_HOUR, + unitOfMeasure: { + customData: { vendorId }, + multiplier: index, + unit: MeterValueUnit.WATT_HOUR, + }, + })) as SampledValueTemplate[] + + const meterValue = buildClockAlignedConnectorMeterValue( + station, + { + connectorId: 1, + energyRegisterWhOverride: 100, + evseId: 1, + sampledValueBaseline: [], + sampledValueTemplates: templates, + transactionId: TEST_TRANSACTION_ID_STRING, + }, + 60_000, + undefined, + MeterValueContext.SAMPLE_CLOCK + ) + + assert.deepStrictEqual( + meterValue.sampledValue.map(sample => [ + sample.unitOfMeasure?.customData?.vendorId, + sample.unitOfMeasure?.multiplier, + sample.phase, + sample.value, + ]), + [ + ['sensor-a', 0, undefined, 100], + ['sensor-b', 1, undefined, 10], + ] + ) + }) + await it('should suppress register phases by effective OCPP 2.0 output identity', () => { addConfigurationKey( station, diff --git a/tests/charging-station/ocpp/OCPPSignedMeterValueUtils.test.ts b/tests/charging-station/ocpp/OCPPSignedMeterValueUtils.test.ts index 412c8b73..5dd86413 100644 --- a/tests/charging-station/ocpp/OCPPSignedMeterValueUtils.test.ts +++ b/tests/charging-station/ocpp/OCPPSignedMeterValueUtils.test.ts @@ -175,6 +175,20 @@ await describe('SignedMeterValueUtils', async () => { assert.strictEqual(result.enabled, true) }) + await it('should return disabled when configured method is invalid', () => { + const result = validateSigningPrerequisites(TEST_PUBLIC_KEY_HEX, 'invalid-signing-method') + + assert.strictEqual(result.enabled, false) + assert.ok((result as { reason: string }).reason.includes('invalid')) + }) + + await it('should return disabled when configured method is empty', () => { + const result = validateSigningPrerequisites(TEST_PUBLIC_KEY_HEX, '') + + assert.strictEqual(result.enabled, false) + assert.ok((result as { reason: string }).reason.includes('invalid')) + }) + await it('should return disabled when public key is undefined', () => { const result = validateSigningPrerequisites(undefined, undefined) assert.strictEqual(result.enabled, false) diff --git a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts index 099b3b53..7e4c561b 100644 --- a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts +++ b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts @@ -52,7 +52,7 @@ await describe('AuthComponentFactory', async () => { await it('should throw error for unsupported version', () => { const { station: chargingStation } = createMockChargingStation({ - stationInfo: { ocppVersion: 'VERSION_15' as OCPPVersion }, + stationInfo: { ocppVersion: 'VERSION_15' as unknown as OCPPVersion }, }) assert.throws(() => AuthComponentFactory.createAdapter(chargingStation), { diff --git a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts index 80745b20..1a6d94a6 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts @@ -373,8 +373,7 @@ await describe('AuthValidators', async () => { await it('should return false for unsupported type', () => { const identifier: Identifier = { - // @ts-expect-error: Testing invalid type - type: 'UNSUPPORTED_TYPE', + type: 'UNSUPPORTED_TYPE' as unknown as IdentifierType, value: 'VALUE', } diff --git a/tests/charging-station/ui-server/UIHttpServer.test.ts b/tests/charging-station/ui-server/UIHttpServer.test.ts index 0874a3f8..1a0fa05b 100644 --- a/tests/charging-station/ui-server/UIHttpServer.test.ts +++ b/tests/charging-station/ui-server/UIHttpServer.test.ts @@ -560,6 +560,23 @@ await describe('UIHttpServer', async () => { }) }) + await it('should reject an unknown procedure with an actionable error', async () => { + const broadcastServer = createBroadcastServer() + try { + const res = new MockServerResponse() + + broadcastServer.emitRequest(buildProcedureRequest('unknownProcedure', {}), res) + await awaitFinish(res) + + const payload = parseHttpResponsePayload(res) + assert.strictEqual(payload.status, ResponseStatus.FAILURE) + assert.strictEqual(payload.errorMessage, "Unsupported UI procedure: 'unknownProcedure'") + assert.strictEqual(payload.errorStack, undefined) + } finally { + broadcastServer.stop() + } + }) + await it('should respond immediately to a synchronous non-broadcast procedure', async () => { const broadcastServer = createBroadcastServer() try { diff --git a/tests/charging-station/ui-server/UIWebSocketServer.test.ts b/tests/charging-station/ui-server/UIWebSocketServer.test.ts index ac9b9951..27f5f0db 100644 --- a/tests/charging-station/ui-server/UIWebSocketServer.test.ts +++ b/tests/charging-station/ui-server/UIWebSocketServer.test.ts @@ -35,6 +35,7 @@ import { standardCleanup, withMockTimers, } from '../../helpers/TestLifecycleHelpers.js' +import { WebSocketReadyState } from '../mocks/MockWebSocket.js' import { TEST_HASH_ID, TEST_UUID, TEST_UUID_2 } from './UIServerTestConstants.js' import { awaitFinish, @@ -193,7 +194,7 @@ await describe('UIWebSocketServer', async () => { const config = createMockUIServerConfiguration() const server = new TestableUIWebSocketServer(config) const ws = createMockUIWebSocket() - ws.readyState = 0 + ws.readyState = WebSocketReadyState.CONNECTING server.addResponseHandler(TEST_UUID, ws) server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }]) @@ -206,7 +207,7 @@ await describe('UIWebSocketServer', async () => { const config = createMockUIServerConfiguration() const server = new TestableUIWebSocketServer(config) const ws = createMockUIWebSocket() - ws.readyState = 1 + ws.readyState = WebSocketReadyState.OPEN ws.send = (): void => { throw new Error('WebSocket send error') } diff --git a/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts b/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts index f328ea43..f567afa2 100644 --- a/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts +++ b/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts @@ -175,7 +175,11 @@ await describe('AbstractUIService', async () => { const service = server.getUIService(ProtocolVersion['0.0.1']) - const request = createProtocolRequest(TEST_UUID, 'UnknownProcedure' as ProcedureName, {}) + const request = createProtocolRequest( + TEST_UUID, + 'UnknownProcedure' as unknown as ProcedureName, + {} + ) assert.notStrictEqual(service, undefined) if (service != null) { diff --git a/tests/charging-station/ui-server/ui-services/UIServiceRequestPayloadSchemas.test.ts b/tests/charging-station/ui-server/ui-services/UIServiceRequestPayloadSchemas.test.ts index 65a1876f..70568179 100644 --- a/tests/charging-station/ui-server/ui-services/UIServiceRequestPayloadSchemas.test.ts +++ b/tests/charging-station/ui-server/ui-services/UIServiceRequestPayloadSchemas.test.ts @@ -296,7 +296,7 @@ await describe('UIServiceRequestPayloadSchemas', async () => { // procedure has no schema to check against. const response = await dispatchUntrustedPayload( service, - 'UnknownProcedure' as ProcedureNameType, + 'UnknownProcedure' as unknown as ProcedureNameType, { hashIds: 'not-an-array' } ) diff --git a/tests/ocpp-server/poetry.lock b/tests/ocpp-server/poetry.lock index d58fd87a..f346d4f9 100644 --- a/tests/ocpp-server/poetry.lock +++ b/tests/ocpp-server/poetry.lock @@ -2,69 +2,69 @@ [[package]] name = "ast-serialize" -version = "0.11.2" +version = "0.12.1" description = "Python bindings for mypy AST serialization" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ast_serialize-0.11.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f6a8dfc5ab204a706f6e5d39c6f77c18c27ef084fa2081803a64a9160ce89277"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:cb073bfa15742699d408ac50f60878383b5665ae1791d1b6799ea6f08633cd77"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1d6ad94edbe93bf1dabc06c9f37d55b898fdabc456aa6d7ced5e23c14f795f32"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40b2801cf2221bd922d9f69d2f0ebc373c3db47207315d525b2d87fa161a2af4"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd666cebd6ab3b3c0fd348a6202c26e18a401ee34293c3804d3472266bc146f6"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d01f61352c96370febf6c0dbd488dee9183a731fb2702170da9163ae317cded"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0fd40c668b0fa19b8fdb61d9e63d547e2e19cfbfe053a51ef0b6c37070298a8"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa819d7c14c8e4153dcd84671826331538be7cbe460383fc6386f5eea5bd234"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a9ffa8a197a721f07a352d0be6185f5b3e6f9aaebfdb66169ed652108531ae3b"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00119a8fb8c1dc0f1fab023f4d8071fa49e3b0208ee54d589fd463c16ab0124e"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0de02520c11391a026e62987a9aa2c3c2ff01545155059ddf0c4bdf2c5ecbe9f"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4e4558956b6a0fb35e18fba58f7d1810b1f2c0e6b52352572cd5dfb6b4ef33a"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6061a54f39e82a9f2cbcb9c268fc441890e4818a6636473caa4f4063254e0750"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:85fbb01e83967a126d71f679f2b9528ef0912cb0854aa1a4657314c34e255b57"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7aaaffc32905159774a107d3cf33dad59bd41b7a0d1bc9885532186753ee7439"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:08eda88a0f290a36c38cab33df8bf7e35eb95bc802ca5beb2c8fcda471a7d10c"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-win32.whl", hash = "sha256:76cc294246e60a914326b4ca88c6a5ea89c064906614aaf1537ce82f09e9449f"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:43b51e6ebe6549bf21416c3c78ee886147b80875a87cc6f69e303dde0d75be0b"}, - {file = "ast_serialize-0.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8df32ad4ff7843734a6c2f067ee974f6d3109ee5a2c3e1a9d2f79347bd282a9a"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:ab924ba260efd7509492f272d4e236d24564033f20c005d7c63a107c6a76fc85"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:a586be418eb70a9f1396cea29ddac8f4b9bf277fb73ea2340db31e218bc00f32"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8532f20916fa3189d4d785ef2a62d93c4d651ec9c5bffda66d2fc36898351f34"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee732ae167e686d1d3c00f98d7d82b23138304694f0441b14d7ddf9c0f8a921c"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75a1c7f46b9c19fc0ae01ca6fd076301628faa2ed7a8edbd55c6353c483946a3"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdf31a0bb85ea2575cc91669f005e6647d2efed491231c4dc1497bc9a5b3aa6"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b78e6fdef3b06c86ed263e1962fee5a7b9d2d158e738b212d13b2c605ee12f5"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:8d62a47714c8bc432b9fabcc29989c815c5da17327d35151f2fd0d85c2a7a5ff"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a5ffa70e76191dcf240d3c43e20c93b3bfd26f54d89148c762d57837f5bcd2c"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:bfbe47a3a7c368f28836e78b2440a3643ac0ec4c67d9fe53588e1448f0a3d35d"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:7f1823275b246f9c7d373be6879e4eec09686948895d4ad083f4b27fd7e4da70"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:57c0f5cb0021a5beb1e5e4d6e840ae2f23a28909703ef4d256a144cc1ad3d437"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:cd320a5c4f1f2742af97eea22954f776379175c5ef2504801e9a155f2ff9a4d7"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:13b13afe32e845c86a573497729e1b7ddeb26c572c78bf50ece51da23b8fad5e"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:9d80a81ec84660422579bdb8e789f656a794b48c7a1ae1261f6bd8bc1897d17d"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-win32.whl", hash = "sha256:af8c003ce721b0099dd55cef4ba733500fc3054ea0cc8565d8957aaf7cccdeb4"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:554d117cb916d8032d85007c654d179efbbfd446174c048062778136a922944f"}, - {file = "ast_serialize-0.11.2-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:d60515335750d431e462af6e722bb55720a5e7827192777bddfd9c4376065a4d"}, - {file = "ast_serialize-0.11.2-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:89499a439955931281986e97ca4dd3c064bf0d2e0027c0017344eb86667733a1"}, - {file = "ast_serialize-0.11.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:daadf1c3e0224621607ffe16f1379e4bd372271ed2e1db8a67878f0bab3ef7e4"}, - {file = "ast_serialize-0.11.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1844ed9a487fb3de7325c52ddb33f2918b66b65cd54d3f8d83d23785ffe99fa4"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b17869f4ba261a5fa468a753328a548f4dbaf74b4eadae9e28aff66df7f1425b"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feb16d9c2a720e0120c58dd5d6e7b3c7c86b43249b60a3bc212bcb8fa031e2dd"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3109fe4805384effc8d0f8e41fbf875aa8f389af91b4348c1cfb60ea6e4cb82"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abdb3e49ba053c3486ac1263bee9f16cc9a4a8abd9f8c90bfc21e3669f3ad9d1"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7004ba572f09be34342ccb98dcd4bad5707d3d81adc8cb4c3f685d2a2c51bbc"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:59c25f47524efa052971b860e128b1add0c94ede7dd16b2962952c85c3582365"}, - {file = "ast_serialize-0.11.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3a367e0e05ed2d1b747ceb07aa728a8c204cc008b589127e9bd4f40053d7575"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:00bbf1f6669f813b48925b759f7ae4591067d456d443924055cab386e7e0a719"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ec1c20f89c3e0d83576e3c06f79375ce936266591fe0d5fd969914af3185cbaa"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c58bb119b73657fdc5569692f316e1e25ca114bd62f7782eb527c6be438ba3a9"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f739e0b601be7300c5697a2573d9200bd1db74b34ab111ef9537b9d5dcd7f106"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:cae5addfbb54cc1d47fe947ef9138e9d83849ed1cbc72b819cf36d96a2315b07"}, - {file = "ast_serialize-0.11.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2fa3be25f7f5351b1b39c9f8a52779b2dbf21199efbae564b4746422e8edca4e"}, - {file = "ast_serialize-0.11.2-cp39-abi3-win32.whl", hash = "sha256:d70556a2f9230a44c99a655774cde823f056efc34466eabfb4085f0cb1ea9f99"}, - {file = "ast_serialize-0.11.2-cp39-abi3-win_amd64.whl", hash = "sha256:b9065dd23131a23b41f5bab3bf4e9b3c350a3fe8e36e8200eded9b729fcea484"}, - {file = "ast_serialize-0.11.2-cp39-abi3-win_arm64.whl", hash = "sha256:dab599cbdcb7b45b18c41fad746645580b3a24357082b7f0e8921cd373804f27"}, - {file = "ast_serialize-0.11.2.tar.gz", hash = "sha256:976a5bd75845d22f4b52905ddf53ab669ef1b14dba7735f5512841a2ef2b5450"}, + {file = "ast_serialize-0.12.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:e73255c9227fd74eac8a9b55c4049e8ad7b66d1f690bf827c98a86b2e594def7"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4655ef993e69e01bb47d2d99647de9bbb74af03938438656832cd010d95de348"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bac5a99a2c91dd823be9b8c44645694fccbb0750773cc5b27889a9f6f22fce89"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6485e681625ed7a094221f16a7ff2ef154946112266a05cf83bde50c959ef345"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a513bc6f60980d01767f7cbe39b17ce0373e722824a74ce28d6cea49ee3c8460"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82866f3523d53ffca8d2a69a750bec52908b69f728012e40959bebce2620453c"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:243054a05a5190f5d087b5c8b16423e7f1cefa4991bac26e9c8ace18074b75b6"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9fbe5a3e8acddfc2fddff3dbbc7ea0e9798b3df3428f851b8abc52a3806f31"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d3d516da3463071d27e64caf54d88cba25cf4ad4afcc807e0bcf67743719f03e"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d6711adf11136c77e3a35517de9488a5081d1012874fae99c2876b64f4daace"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cbe239bee4bd609186daf60b95b7b0f47146c7f7f55f6da83807d747d6fe753f"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:5bbf582286c9dc6b4c544ef645dc99e4b3aa09db28892bc60344141f6926641f"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:99e33c93efb5254a70c525b46038212371dfe5693d48eb2d0d5f17d936a263d7"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:aa6c17a2b7f07e81fa8cfcc4aa7c832b3e57733853aebea113ab502f9b0963db"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f896fa38e0af38821e1ab1425c5dee89e359623e165765bdeae7d0eb6909e76d"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:af699e81fd7ce80b8b03945826d8ea23dd36d072f10d4613402da597ba4ee9c6"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-win32.whl", hash = "sha256:10b59afc108eb285146acb23d1b5ec0fc58bb3c09cb2ab8876402df06c373c3b"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-win_amd64.whl", hash = "sha256:72e871f6995a066c1b19104f8a6b5832b1163adb9a8267c2aa4711fbb0f4d1f3"}, + {file = "ast_serialize-0.12.1-cp314-cp314t-win_arm64.whl", hash = "sha256:3398e458047d21c9bc1b323fe5aab77c608dc9ddb65b2d44deebaff503a1f1eb"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:410233de149ab8414cb27c6fc73e9d2baa35d6f971672d540d752060d980ffb4"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:b9a2310845302f1a6bd45ae8a67d5760211103a8d66410b854bfa440d107e093"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff65f40f49d5e1a1a043ba366081d59a4e26a9f5c1b07eb1170e172115da7ca"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:536d783c4d91331f094e0a892221e619be5ffbe6fb6640885f14d7f226ec90ca"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c42d2d65f388d1960c5796231eb9bf5a988c46228633eb489605c4549ad16c52"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5628a12acc875fe7a167910f18d101dd101c2a7b1e6c2b6f7289ffaff25805c"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e855adfa5bb982b2e6fe09056b2d584f6dd4fce085d91a07d1155683751b6b5"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:fafe1471e8aca6c87b4913b7b54ff97197adf702fbe28692284b929dfa62ff96"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5cac246474d2a703147d1605a6ac5ba0fa9e0a443cf1cf42513adf4df02686f"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6e25cd319fb0d7b39fcac666784ec86708ccbc78d07a698b1400cf5ed40c045b"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:657a7354ea16ed4d29f8127ed477c6fee3915c111d135f020ca835a991438e90"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:9eb9de7e59621acdb3e66984f374272b33d56d15a04763e2fd0604211e1c8303"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:09cc4d3103c1fc97f6845ba307af1db9cde5226bef47f8843220dde83f2276ba"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:c9e2a592706fd791c2271ce9c8f4e38c98d3ea0b4a86b511e09a4fe3ac44ab37"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ac042e95a575432c1730ca4cb9183066a2074886a599f46c1f1b0955fb8198"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-win32.whl", hash = "sha256:b3cd105995942cc6163a229674a86161ba1305f646493efd59bd6723a357ee14"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:a9cd24a26126088693ca054547ea0a391398a29cf1a3a2bec1009b4b6acc8b82"}, + {file = "ast_serialize-0.12.1-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:9649cd903db0dc047906c6dd740784a2ba665d54f7e43ba31457edbce76c9493"}, + {file = "ast_serialize-0.12.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:5ef62601db3ce5c23445132262a193075e211fb2fc87b46b7550dd351fac0976"}, + {file = "ast_serialize-0.12.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:98d91cd3a6cb76a39512ee090a539d1e3206b732ad8150eb38918cffa1ddf515"}, + {file = "ast_serialize-0.12.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8a32f184ce3e4b1d0b06d642a1243281cf55b99e0323680b1b8f904029fd7700"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e62126ac2be2d9340ac1b3ee7a0466a883ecbed634cff0929c88ca0b671483b7"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f93a70fa9826c04ea9f2c3a880f87f4cca09a828144ed5084682abd28110980"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1858887be56a64a2aea899423dfe43787c34c75c18b0d7497de8e618d54b2790"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89a2bc39a820bc7785b60c53a5742b4e8dd4c1a599294e2dd68fae545883d44a"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a9eaedf1db4857dad7cc47dd757ed70bfcc40b89d44d516c4a2f0d5033bd76"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:c30b609e8fea426b310543126de876592236a25aa8ebd59f1e2b323dd52a4085"}, + {file = "ast_serialize-0.12.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b1ad06513022cfa1337744959255af0ef16119d2beb1e547b67f37ad9433d4a"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6add54b495e37ae3cf3a1f0d5eaba364814eb72e93026adc41b7791e4b0d45d3"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:5fc136cd08001b817ad0b3e7426f50a7d2b8982dc7c6491f0af78af4c3dd8672"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:45a9e6b700bbd973d49942668a5cdbeffa693f2e250b8a5409abd1fa9d351854"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:a1d8267f83c613ea0a31f2518df074bd62e98a4b3a4892f6a529d74e08e02dba"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a0cbab9796e6ce841197feeba4008faa96b4cc7741129542fd81c882d7a4f01"}, + {file = "ast_serialize-0.12.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b4282695f1d51a3c6ef76560351bad5af880eff7d755aefea325bffb9bf68c25"}, + {file = "ast_serialize-0.12.1-cp39-abi3-win32.whl", hash = "sha256:119d1b0cadaba4a6e475f9bbe79eecc79351e373142eabe7c353f0250d70aebb"}, + {file = "ast_serialize-0.12.1-cp39-abi3-win_amd64.whl", hash = "sha256:3d6ed63d4fc1ec867b8cb522d58c36df0e8f05e487bea0ffd102043a37636d72"}, + {file = "ast_serialize-0.12.1-cp39-abi3-win_arm64.whl", hash = "sha256:610a41351de68199de9a1434499083b4256c0df7658ec1cfc0a0a7b20b08d317"}, + {file = "ast_serialize-0.12.1.tar.gz", hash = "sha256:5285a390caf1c44368ae270f037f797b91427d138b7d43cad0f1fda4c83518d9"}, ] [[package]] @@ -93,133 +93,133 @@ files = [ [[package]] name = "coverage" -version = "7.16.1" +version = "7.16.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f12a9e27ca7b65e40a8475d27899b2d45064d9020e6a89148939e01987b5853"}, - {file = "coverage-7.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f590d46c30d9e4c1fda3efefe5443f4c2f6a4192c5ca2ba403653e9ebadf097"}, - {file = "coverage-7.16.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df82f0a3cef4e1bcfc799436056f0b978dda319d0bfd4460c6e479b2802d98a"}, - {file = "coverage-7.16.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb462d59146656e278d1e8ed913ce374d0ba68a4e081acde1867f6d3377fc881"}, - {file = "coverage-7.16.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7db888dd0a1df1a653cae7f99d4047430d2187a3626eda51bc847b0fd6b9b43"}, - {file = "coverage-7.16.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:38a7e16f061504ac2b45370bf5bf97e8250d8d3f25e37385bae884978166554b"}, - {file = "coverage-7.16.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b26b55b18e1a53e1a159dd743728c4ddbbef28ba19000a91aaff5ce023197ec"}, - {file = "coverage-7.16.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb2f528b50953e29d22033b3256c396a700193c6e45b2490222ef9c333cbbf9"}, - {file = "coverage-7.16.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3284754371dc78592aa3ae4d661d30ee20e02b2b6b0de3590a181a936d0d3b38"}, - {file = "coverage-7.16.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:802d1246c540e07486d4ee1adfa19a797e4b33529ffc371dc140644e8f27da0a"}, - {file = "coverage-7.16.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6fc735d6fe6d57f803e7ba021be4dde48e43e6aff94e6954350555d5332b0594"}, - {file = "coverage-7.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed5ade1bb18f62edace1bd198c66f9d4c75a8385d5fd24e87d917ea1a5958773"}, - {file = "coverage-7.16.1-cp310-cp310-win32.whl", hash = "sha256:0c309096926b119543dc16438a11ef4c80783d2f4e59ff94f7f462651a944cdc"}, - {file = "coverage-7.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:7562f8067ed9360e8b9739e5703403a7686dd1b36bf0f89fc538047c54cdea90"}, - {file = "coverage-7.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72e013665e25cf9d44779f01f340af26319756f9a76822b7c94ce6b1d93813da"}, - {file = "coverage-7.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb05c0ff98b56ba6969adf35556bc43bcb8d094df8bc9cb403acff53460c4e07"}, - {file = "coverage-7.16.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0ececb32090e3fbb03e0d352b973a0485879b4de6c58daf47227b9988b99e5"}, - {file = "coverage-7.16.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f0ba3892d81aacf36996c52f16bca04e39af31a6c5de930b7688ab617f4a6475"}, - {file = "coverage-7.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6410b75fe07d5271eaa95fc24bd0a9177ed588d9d1c10c0cf67829adb8f0567"}, - {file = "coverage-7.16.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1039cb2de093225d597109342ce1675626bd127565e80b3044f4eca07c15b2e"}, - {file = "coverage-7.16.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf047bc39fde5425be2628666d0f435ed8817859111c3aacc84b32d858069f5d"}, - {file = "coverage-7.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c05913d0d5badf7ac83200f35dcf9514cce5df16a7cc89e7d1d7fff0461813b"}, - {file = "coverage-7.16.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4027bf6d7bc0a16df058ce913b69f10c5687f8e1ca668f08caa659ce101744bf"}, - {file = "coverage-7.16.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4f48b345f831eaf4402ab6333c2dc3e2e2b5bc7b9c1b8fe12680dee3f0538f01"}, - {file = "coverage-7.16.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8643baeb590726c558b2faed6cd59b0917480f9367fcc692026f1a86d824fd08"}, - {file = "coverage-7.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d06dcc420b570bf683cdb647cc8fe62b672d9e429ef711c3cbbb7a6880ca1572"}, - {file = "coverage-7.16.1-cp311-cp311-win32.whl", hash = "sha256:946f58aa59b08bcd6afcc6a7bd0ff54ed5eee844f32ad69fe6814836d15856a2"}, - {file = "coverage-7.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:684c7ee9b4c04358fe6ac8b517ab51ec35fcd79d08ff0f105dd8bcd96885bbb7"}, - {file = "coverage-7.16.1-cp311-cp311-win_arm64.whl", hash = "sha256:1b24f79e25bcf6c73931aeca7a3dfc7595c0cb5e9364aba3fdf387a3de4b1c22"}, - {file = "coverage-7.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7f2c26ce6ce0b1e0ca0d5fae96ea510e3a2e78b7207f06e76b7f2c87fa3d0af"}, - {file = "coverage-7.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070acb9da788dff743a4d36fc015feee12d68f0349959017542017c79f59c21c"}, - {file = "coverage-7.16.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e366587b370bc9b8b51b7b7272c610c56db5d5b4795b9e4a29d28ff2f440f809"}, - {file = "coverage-7.16.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e82e10b9d290f60b63459cfb245a841aec347603997206296b93881463a93dcf"}, - {file = "coverage-7.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d73bb1f85c4150ac208fb0755beb04b2e44897bad81414de9380f98dd74729f"}, - {file = "coverage-7.16.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3397b9032553d281ad6a9253b12675b65e0cc8cd7a3b0633cf48872c9eb13360"}, - {file = "coverage-7.16.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77890395cf37026a5907d3ad32376aa51f41c0f163b7477fdbd4f94966cc1d08"}, - {file = "coverage-7.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b0944dc3bee3091039bf970d73caaf930c906013128a421bdc132e797494d941"}, - {file = "coverage-7.16.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b89d22a89d5bc05dd95b64e08295b8394aa96dc88e08f8ba210c9ebfebbe0489"}, - {file = "coverage-7.16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:550a2a1faf7559f13d5344f12d1eb886ad87955155d7dfab2a3fe5c8ec8fe776"}, - {file = "coverage-7.16.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:55eb268e5b81aefac759766c9162625b06c1bedb7b77d936225bafc4f038a6f6"}, - {file = "coverage-7.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65a8fc80898c9ce59f04349fe8b4849b1f9787f14e52ead990e5f849ff4727a0"}, - {file = "coverage-7.16.1-cp312-cp312-win32.whl", hash = "sha256:528a61be40977c340cf201d23b69bd6a6bab507da60e9dbda85f8b30e935d70d"}, - {file = "coverage-7.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f02c633630e2b74522108ee95a84ad6e1204a8016a6cca5297f335ea27147e"}, - {file = "coverage-7.16.1-cp312-cp312-win_arm64.whl", hash = "sha256:2959978f9d1d20a2c0c15d0a68baaeccf615ac1aa214cf4a05a10d6f568926c8"}, - {file = "coverage-7.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ee5465db6e9152a7d09f3215309326878c6aa3ac509195a369f9d264ff4bfbd9"}, - {file = "coverage-7.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b8256f8b525ba233d2e4cdcdce0d6673c66fc9bf70df1fd5e67c54a74e2d245"}, - {file = "coverage-7.16.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57cc400275b9a2892e905fc893f732b21ddb95271bf96406c88e2f6367848b5"}, - {file = "coverage-7.16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b3fd0f3435ebb7a7183b32a6062a8b755f08242ced1f3f22761d30b56b3c2a5"}, - {file = "coverage-7.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5eb1762e7eb5fad34ef913e8107c7788a66f19d328e598ce95bf7217f9e5c8f"}, - {file = "coverage-7.16.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46cd3a73e9140410de62cceb66214bce0e08fb3922b9176fbfc1522fec151b41"}, - {file = "coverage-7.16.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1ba5142d68dd2cb775cbd0ac8601819298152047803c8efe4eec6d7d7aa7878"}, - {file = "coverage-7.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c389c6f9d1d518e1249ddcb8a7f158135644ce2c508fa6cc17b680777dad5bf2"}, - {file = "coverage-7.16.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cdc57746c7ac0ea063351b4d651c3bb4dd4fd35e64dbb8e90c10e14eb03c4080"}, - {file = "coverage-7.16.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5597180ed7670cc94c04c65347418a467d3a43d5f0cf52fcac647f5425f42037"}, - {file = "coverage-7.16.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9647a0ac46255b8fef59a433a2161f03e5483f3a35e1cbd9dfe4600baff0c6b"}, - {file = "coverage-7.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e306e98186b9cd109121f3583aeb7978797ad21d948f22944c5c08845cd554d0"}, - {file = "coverage-7.16.1-cp313-cp313-win32.whl", hash = "sha256:48a78a66fcce49d7f6156524bf979c0ac633d584199717c68c6ffa949fc14e6a"}, - {file = "coverage-7.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af03247d598a353bbbbe1b925deb735276e4d845e7197c4073dc89352b236fa"}, - {file = "coverage-7.16.1-cp313-cp313-win_arm64.whl", hash = "sha256:166adae25b05b04c9a84135912066d9c97482115af38df1a419a38aacc6b6f5d"}, - {file = "coverage-7.16.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cc0b37fe6f5ce5f1ccc62ad4fa9b1ad201d8e9b6027fd5e0170877beee4b2d15"}, - {file = "coverage-7.16.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6618f481053b63fc6121faf8fc676bd9b7163c2a19d9e984a2e850002c28ab57"}, - {file = "coverage-7.16.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa02d561eb1d8d2f8ba43ba6e3cef4c6c402a3b632a9460fa329fcadcd5df6a3"}, - {file = "coverage-7.16.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc5354a124799f1f87b7637bbe6f18cd4bc66a1f37f6aa2b5db40f9adad531dc"}, - {file = "coverage-7.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34bafe9f4094315248573e6223e11af0ec1b25f9cbca43bf0e9a26a189ba2751"}, - {file = "coverage-7.16.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:29c4d3e32a3b5efa420a3dc627c7e570deb80ef997def52c7686a474f5edc7ab"}, - {file = "coverage-7.16.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2066c447fdd0bca39a9633a082d8ce67bf9a539a203b85059a364a405dc9fe9"}, - {file = "coverage-7.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd8ac10cd2458b3c6343aac082fb9bd0e3fa806cb2c4975f2280153474b88412"}, - {file = "coverage-7.16.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d8c54ec32e5c102b9241f75d88ae26538b53662868ca491736611db448d9c7a"}, - {file = "coverage-7.16.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6dd8dda3402a01a1a8fe8b753a282466f615128574a5590a9108acd07b1f8540"}, - {file = "coverage-7.16.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:79afa9726438912e5cddd1fe541815cea9763c92935f594835e4c432565b68a9"}, - {file = "coverage-7.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3db3978211c3cead5437a80136ca0556bab8bc7828de15a762884b0598c41361"}, - {file = "coverage-7.16.1-cp314-cp314-win32.whl", hash = "sha256:49c39c7068a494f8eb427155f5682f44feee43f9b3107fd54b1e52465379c54b"}, - {file = "coverage-7.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:c510dad19552d912058e4c3e3cbec3fb155dbe8d0ce0ceb7e7dbf5c5822bae0b"}, - {file = "coverage-7.16.1-cp314-cp314-win_arm64.whl", hash = "sha256:b7d4d7e6dcaf33e85f1919f03346403bdcc27437c420a78835f3805bca0ab71f"}, - {file = "coverage-7.16.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3d0a3681c12d3e0bcdea3d9414b04087828d6c1a482802d6f7f42c37ed530152"}, - {file = "coverage-7.16.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f3b4469d3da3ecced775d1a8c9c5d9fc80f259e30b7b89f9fed0700d6035ecb"}, - {file = "coverage-7.16.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c08ae35c1be2fe1ce4b4c628df5c6fc0dc9a87f8e5fe8e20238d249678984741"}, - {file = "coverage-7.16.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ee71a38c54bb2676bbe762b8b0943a79ccb1c2fd6a52054f66e63eda392f8c1"}, - {file = "coverage-7.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76491917771f179f9772efe218c5ccc65950dbdb35f4439298d8a8dfc6ec1f72"}, - {file = "coverage-7.16.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4aa0b0a6f81fa3deb211e643f6954e78b4376b62b9c218271236cfa757664e8"}, - {file = "coverage-7.16.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:756ba2d96d073c5a2a55d67fa22784763710fadbe22c41adde2d9cfa4dd78a8c"}, - {file = "coverage-7.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:99bf9ea435cefcefd220f8687c3ddbbf78dc2de0bd11b57c3ae9fbbdf8d5561a"}, - {file = "coverage-7.16.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:35cbc81f937fc402971df45c897d2df2bfb2014efcd990360032aa0a651635da"}, - {file = "coverage-7.16.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:8fae08e85b334ac6ac886002b5041396a31bcf805225bbe19847627203da99e2"}, - {file = "coverage-7.16.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:83362b64e215ef00b0ba33fcf13655ace6c9fdd144d5ad2ab59ac86c2daf166e"}, - {file = "coverage-7.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:33300f2e140ccf26af3d8152e62bff71993f9310cfc63ba7a20940b0d246a0ae"}, - {file = "coverage-7.16.1-cp314-cp314t-win32.whl", hash = "sha256:5539304fdbb2cc144df684d35a33b81145334d23e1c2367b5a923d25107f70b2"}, - {file = "coverage-7.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:715dcb72c3280c428c3a20134b87e42c29acec9669136e899ab2de69ca86218d"}, - {file = "coverage-7.16.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dac8b84c03e6029d272b8249c77018db83de59ca009a9adef7c144b4a62ee5e6"}, - {file = "coverage-7.16.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a337dc2d54c74430cd2febb8ee04f7c508ba8b3b412bf0463f077a66cfc73743"}, - {file = "coverage-7.16.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3acd1d78397dead78dd1b011b5fc19cc823c190349acd549e63856dff649c80e"}, - {file = "coverage-7.16.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73a32694603a34ad01d7e51a481a4023410d8099e1d0757e067945695c10f0ae"}, - {file = "coverage-7.16.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbbe8265736659a6be2e6042b6a35be13545d14b243cc1d7ecf65f90d788a370"}, - {file = "coverage-7.16.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b0359eb4c62f9993e176bc8f50450fc736a6b90dbcc05bb8584e948812699ae"}, - {file = "coverage-7.16.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:da506e669a8a851b59e122b4b219ea70996a6296f44f3a9348a852526ff961de"}, - {file = "coverage-7.16.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:64a2a5985d81810ed605ff0dc4ccd6555efcb5700353825532a9a0aea65826e1"}, - {file = "coverage-7.16.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:66d70132b69b861805dc1ca46cdd733e54c416890e8f1371d2fd103f70b59c9c"}, - {file = "coverage-7.16.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:db651a9cf325a542bc2b7b8cc8f1b2bdc6492739bae3103731b2f1c85b96cff6"}, - {file = "coverage-7.16.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:4184e78a4465dcda359fb403172b8951dd220929cb0984c02fabca1742fff06f"}, - {file = "coverage-7.16.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:bc53c3f3adaa939b7a063533ffe0ae1259e7073393c618043a99a6970a87e3df"}, - {file = "coverage-7.16.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b44308854ef210b9b78df9cdfd4e159513382a859f5ef8464306d14a54c2a040"}, - {file = "coverage-7.16.1-cp315-cp315-win32.whl", hash = "sha256:dccc142614d3419ed71857deb43f1d757829a4c7fce9994464b71e7e38309827"}, - {file = "coverage-7.16.1-cp315-cp315-win_amd64.whl", hash = "sha256:961fc424e9d5229a99f8f1189942d8e7f4e1519147c3af64842f944aca03914d"}, - {file = "coverage-7.16.1-cp315-cp315-win_arm64.whl", hash = "sha256:681a9488c5a234397c4f013da065aa9e53eb7af4c78f1f80c6f15e7208acb855"}, - {file = "coverage-7.16.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:8bb09a2d19b04db1fa0e087a7ca4f12458f7e0e7364cfcd838441d86fb1c61f6"}, - {file = "coverage-7.16.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2270a794600b635ca9452ce4c32e2fe81a35f9caa17ffac0eba99f14f275bd4d"}, - {file = "coverage-7.16.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4eff405b545dfcf79cf0d9d3ff750e5c5a887aa066114175193a81d249c5ee6"}, - {file = "coverage-7.16.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ee1d5fc9e3bd6a217906929cc97880239a91d20dae7746f538eb0eefee705ab1"}, - {file = "coverage-7.16.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4ec944947de098ad5a1738413f9364689a57067ecbc328e9de37218aa1e5cc1"}, - {file = "coverage-7.16.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f73ee3956fde2d461c9e2955dd48166e4821fc8587d135e8780fb84da2a098b"}, - {file = "coverage-7.16.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ec9a4ee989c0d06ad95add0dbfdbb72b00ef53f43431ca0b612384e7878e5de"}, - {file = "coverage-7.16.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7e5727b2508f817f3126d6c33327dda32fe69d15514badfcd61db8bc4209ecef"}, - {file = "coverage-7.16.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:19a3ea2f364012ef06678118fffdc92442a16bef4a5c8ad4f4019dd8f9ac8876"}, - {file = "coverage-7.16.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:996c2b891b441ec2b39725ee3e8386e2f11b4894b92be225fdfd54a3eeada2c8"}, - {file = "coverage-7.16.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:a125fac1f6b1e88488d208a86b578e1790e3c4937f2e1568d23356141d236220"}, - {file = "coverage-7.16.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b10095528b866d322d33d6bf1709b7f8cbf959f12e8cb2ba22fc59c8717866b0"}, - {file = "coverage-7.16.1-cp315-cp315t-win32.whl", hash = "sha256:531d9be377fdcc05593b974656872eb82e808ebeb42a72515e3aaeb8bb7166f5"}, - {file = "coverage-7.16.1-cp315-cp315t-win_amd64.whl", hash = "sha256:46a88f51770df7c9bc376bd57d3f86cdc7624b8e16ac4b585a655c22b7a1b4db"}, - {file = "coverage-7.16.1-cp315-cp315t-win_arm64.whl", hash = "sha256:7580432cbe1e8b762660ae5806f04f869e1c02e519836a43f8094437e561e9f0"}, - {file = "coverage-7.16.1-py3-none-any.whl", hash = "sha256:3d8bd4e58b6a5c2018d808f297905393c6c61da466a48c3f0596a76a4900ebe4"}, - {file = "coverage-7.16.1.tar.gz", hash = "sha256:f83981779bcf9dfa06fa0a8d4cb43e0faec1706328ce07aa3e7b665b4ac0f210"}, + {file = "coverage-7.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:23219888477edd736b6fcaec1272d47d93b926e999641ffea7e53a1738e70b2b"}, + {file = "coverage-7.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40c0f00899fe6181ae7f434ceb200e51f5ee4b8ed10e3b5f0b605f0cae15da87"}, + {file = "coverage-7.16.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4624f80732f6b427ac58f1f59c577a0994a12e8174b5af6a027b4b58795d4c3"}, + {file = "coverage-7.16.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:191803c4996b499fcd78c2ad5e5f767dcc53cb4dc6de6d6a741b443a1821ef02"}, + {file = "coverage-7.16.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fd670ac43b709c575aefc25bf52d8a598a3bc5017bddfd0a179152ab06a2deb"}, + {file = "coverage-7.16.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:705e5af11d34647efdc170c7840b6857c81cf74be96419a553f237e68e62cb72"}, + {file = "coverage-7.16.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8afd9bf35cc6a1f22eb3634808fa8e0b91902459c5721ef2e4461dfe771d7f08"}, + {file = "coverage-7.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f43bac1856ba269b905302778d4df433d6006489a192174ad77ac528e395032"}, + {file = "coverage-7.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f8475460aa33ee28ac896ab1156d0bb3b6c639f7f8383c2677d3359eb35f8205"}, + {file = "coverage-7.16.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d6276d78f6fca7d0ac066d5da4165c5acd07829e8305c2cb900b738fb3a75a72"}, + {file = "coverage-7.16.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:736fde09ea39646d11f8e3b76bd3425c075aa4dd45f24891970bb77c14ff20f5"}, + {file = "coverage-7.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c85d54e7e8a2ca932fe8399301af9b8d5907ea2a455ffaff6e7d1208db83b943"}, + {file = "coverage-7.16.2-cp310-cp310-win32.whl", hash = "sha256:5139009b5efd2194fc168ee9362f0e191ba612ef5d29242f9269c22f9b8f80c7"}, + {file = "coverage-7.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:c3305c38a2fa21a4254f2ace7dd9ef5fc569c9a558b66e7017650b3d637fb95e"}, + {file = "coverage-7.16.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:732d950e51f3ba4fb6209c73250f3e8924fefca42953ee04a9e65d8c02414d7d"}, + {file = "coverage-7.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5dca0bb66b4c3d624ba047887bf70270030c150692d543cb501293dc38a9f4b5"}, + {file = "coverage-7.16.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:af2a2a8c7c74de0559e0c368d94c8def9e16c58faaee33a0bf081057c4227e3b"}, + {file = "coverage-7.16.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db5f8394e17f877a625b257f2ba0ce8e728a499c2c1579ad66220272cd3df510"}, + {file = "coverage-7.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3146d2317c75f70df2509066d979dadd941f7021cdf9b5db4bcd8568258e25"}, + {file = "coverage-7.16.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e1d0ced76318bab499693ff25f64faa343415187cb2e4d7befdfdd391a1cf6a"}, + {file = "coverage-7.16.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:af98ad5ed9d6daaca956201e00bb429a7eb2b080426686f70a20353e0f9839f5"}, + {file = "coverage-7.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d56e4d21c56d2046447733f8b118409597db48c01efe898ee9ac24e858ec2d6"}, + {file = "coverage-7.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1d5d0e3b660506fb84f995814e3118a21efdc0c8eb80127da1be627d90093c17"}, + {file = "coverage-7.16.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:17228fbca0f22976f797be94e975dcd237799c657d49551c7de1e0654d1202e9"}, + {file = "coverage-7.16.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:bc0b0ac781d489304b741269857f1f8338b7a26b1b89c06c0344658001ec0035"}, + {file = "coverage-7.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bf1bd822ec4e387ed245bed0d71151582cf7be9e5309bc4145eefe36083d5878"}, + {file = "coverage-7.16.2-cp311-cp311-win32.whl", hash = "sha256:7ed238d227e23cc300c3d464babdaf9f6ddc740aa1b15a77ae96136e6a7c4516"}, + {file = "coverage-7.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:a90700f743e29aa3d75a6ff5f01953176a889c00e526194bc4d281731b88d99d"}, + {file = "coverage-7.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:a336eec40e3520d369b8a6cdabb4f596e69a8b42927ca074aa1452fed943238a"}, + {file = "coverage-7.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:218d742afca2b5ad5ca759e93eddedfbcc6eadf8322f080dcefc40b7bd4e2d48"}, + {file = "coverage-7.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9a638be322a8d76a41cdb17781c7f82aaee6a66493d8ffb7e2c09ee22423d99"}, + {file = "coverage-7.16.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:724bd0f1e81856b35e59fc98cf7b4e544a3cb662e4e0864dca73d4326ee9d808"}, + {file = "coverage-7.16.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5375ebd99038021b35e99dc88255022912c06565d316212f4a576e4b08d30f5d"}, + {file = "coverage-7.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a076277ca9f5750cc230f0f578ebd2620cec60255b25707361699fef6fb465c"}, + {file = "coverage-7.16.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58d4a54c6ea672afef66d49be922a2c69826c5ae1a42a9cd94f0c9c2bacdf800"}, + {file = "coverage-7.16.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcbcfcc059117284c603ff8cb61a65872512882f84a8cf0339241f7f7c2f148"}, + {file = "coverage-7.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afdf43b72ef3876c1fe66423b91466e37877c9e81e8cec70542b7e8525b9d1b7"}, + {file = "coverage-7.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9acc7f7ec4a1b5f89bd929fde5b8a714f6fafdc6cc18725413d510aa082b47ad"}, + {file = "coverage-7.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:80d3f7b48d43ee8fc5e8707a8adb43d743a5a1a85256c25a24f9d6d0e2238fa6"}, + {file = "coverage-7.16.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:126d1af8804d7224421fe991ff65d3ce649081560df7a98b1a5ffff07f9923bd"}, + {file = "coverage-7.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c19cd6d025c1673f22afcd22c7df8a662d779e05d8e3fa6820c22afb895b0206"}, + {file = "coverage-7.16.2-cp312-cp312-win32.whl", hash = "sha256:152877cdc8a07264882cfcd503ba56a3ef6cba56a70e8c70f6eb8ffd7384789a"}, + {file = "coverage-7.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:e6c52d3307824ff93b39efd99e4185d557db40bd841452abfb32e5d9151ca162"}, + {file = "coverage-7.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:a678c0b6b22086ec2427359d22e37445d4a792f5fdbbc744112c7dade65cad02"}, + {file = "coverage-7.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a37c6e478cf687e1aa30a593d19c92c02fad9d122b51ab73f51b8dc7a0c0fc9"}, + {file = "coverage-7.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0993d0e90858c03943d3cb152e068a20dd4707924deec84dd2230261baae3b1b"}, + {file = "coverage-7.16.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bb2fc905bbf4e6b7f40806ea79e31515abf6349594cdf0adf27c4215f0463204"}, + {file = "coverage-7.16.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4358b9c8c0125b460407f3017c6cce8156e904b32772c5630d27112f52bdbfe5"}, + {file = "coverage-7.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f15254427c9b33eedac4f198eaf9e356eb4f6214551afb43da6194a2c088ad7"}, + {file = "coverage-7.16.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a75a4704ff640e46170042eec1f984385a121227c505d5a16ad8e495f452541"}, + {file = "coverage-7.16.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:14253fc7bb15749b849795a06f5d3b6d8bc3fb8a4b5ddc341faf7a89dce205fc"}, + {file = "coverage-7.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:921415102a90637fcc2e3f169f61dad7699ecf690e8639fc21b813acbedc0967"}, + {file = "coverage-7.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cce2bc991293f15cc4084ca116827b5900c5f34e1a54dfe83f10ab5c43162eb7"}, + {file = "coverage-7.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1fa594c887365b69745f25a416806e61085dd07b94c9eae68a6e20730629b23"}, + {file = "coverage-7.16.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:11e597173af1dc33d5f8a7332ada544199269a223af1ee1770ddd5e245ad0fe8"}, + {file = "coverage-7.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e7f99698ba3a7d13988bdd984b7ebf13af4dbe2166dc8502eef90d77603b0a4"}, + {file = "coverage-7.16.2-cp313-cp313-win32.whl", hash = "sha256:f80bd9f9633eafc73d0a913ba2645c96ba58bba1befc30590f7c0fbfde59d865"}, + {file = "coverage-7.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:8be099e979fc42559328a21828281b4578304191ae46ed4e80a407048a82eee6"}, + {file = "coverage-7.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:28ff850182a67d117990fa2ce5ea1032836d8c9630dae867e8bdd3bff4533b79"}, + {file = "coverage-7.16.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4ee546b9e4872ffa194bf07ac87bfa1202ebb824d0795dc1ef22f175545ca90a"}, + {file = "coverage-7.16.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a2fac6895eb299a2e52d7bbb8fb3903502b9da8d3f5309ceb16ec40c646b58ee"}, + {file = "coverage-7.16.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:57ff3783f99d75a1e81dd56a9737eb5665e6736a5d93258ba596b6dcad8fd05b"}, + {file = "coverage-7.16.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f37886699cb9abd29958247d718628d5bc6f39e623dff66a09e546c42a7e03"}, + {file = "coverage-7.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd7a86fdda7cb6d616d178654bd0ad6bc0f3f33c2e478aa598500a1a9e34eda"}, + {file = "coverage-7.16.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ac0f3b379c94acc2f7dce5f5f0b24d44fa1cc6a509717ef83dfee07450c2117c"}, + {file = "coverage-7.16.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d0732c83746bc24123c581a85d9dd96b70ddb538c9076020aa1a041790361e9"}, + {file = "coverage-7.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b451c68218c150f616bc9649783ec8de76a59792c759b43aa0c9c0466a465e4"}, + {file = "coverage-7.16.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a56ac4fa5a75c7e182e8f62600cfb4aff43c5ed7356a034f3557659c3bec1d90"}, + {file = "coverage-7.16.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc4f73aa3fabc36e32046d6cd2971405948d8a903636508a3d3b2f9128b3a95"}, + {file = "coverage-7.16.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:723dcdab91357159b722935b500ee8abc0a66c8c432e1e9fabf4cc7598952de8"}, + {file = "coverage-7.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5397e21a90dde0e9c6896b77ded8f0be26b66f8b22b33aed41f6043ed95d55e6"}, + {file = "coverage-7.16.2-cp314-cp314-win32.whl", hash = "sha256:848893e1d361448c113dc2f0913503522a6f7be231d0e38333d2a22d9698a011"}, + {file = "coverage-7.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a27b731c171e43dc8b5f32b76a5051dde2ec9b9366c87028f08a7088ebc2c7b"}, + {file = "coverage-7.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:1c569a9fd25505f1cd6bea90588818f90373ce90e2632e2cacf19ddbd6e14fdb"}, + {file = "coverage-7.16.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d93db87adb6b1c1b408dce4763314b55d76a9f589e96783a84ac9e7689e48bdf"}, + {file = "coverage-7.16.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aa62c85046473959c13ba9edca9dc90a77d5c1095b1ba313556314d77fe5b036"}, + {file = "coverage-7.16.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db76506aa5416081f3e8974ae0f7965c58ada0bb0ef7339ac86099588dbb20d3"}, + {file = "coverage-7.16.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a0f2285329dac10ab08f79cb11f5692c497018e6c7c511f95e6fd63a70b8f831"}, + {file = "coverage-7.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:382d3346d56b0eec1b793d53a4c88799c8053f516aa3a8d7c44315696954bacf"}, + {file = "coverage-7.16.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:648352b94507179d82637292e7ae8802508d95f78e2f00a705a50b6c48011681"}, + {file = "coverage-7.16.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb2bde05838fffae1a1bf75e5d411a6cac3e4e9bb97e6640fed8cd47888b33f0"}, + {file = "coverage-7.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6a75180829efb8ae62b4aded25be6ddca1c888d138d2d82e21d93bfbd88f41cb"}, + {file = "coverage-7.16.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:99704f73721e23859112072d522076e11c31744fc96b5652e5dd2018aa4359f7"}, + {file = "coverage-7.16.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:29309ccc86b7f33df7db12813c299f215bbbc470ed6292d0bedd63ffae1ebf64"}, + {file = "coverage-7.16.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:30c1b65d529e46569899fadca59e4a87c1faf2886923f1307ba61e654d4f3c20"}, + {file = "coverage-7.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcf4bc2aab4e16b1c4c0c2005918f23a7dd5d7821ddae82caed9e3342dc2fcce"}, + {file = "coverage-7.16.2-cp314-cp314t-win32.whl", hash = "sha256:a9cd3de0a5bfe7b0e21ee10e1a14e3d61bf52efc88217ab1d95d6ace6970bd46"}, + {file = "coverage-7.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:611a44e5229a59d7483ce830160e1a0e85f700562c7a5651c7c63fb8f4eb528c"}, + {file = "coverage-7.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:22957cef43ce038641de78ba995de7568d2d6a37c6ddbf7fa0fd7d1ae2344d91"}, + {file = "coverage-7.16.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:414c26dfdb96aac2d570a54e03008f001e32eb2d413705365503648c6bd361d8"}, + {file = "coverage-7.16.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00d3eb96e9988c45f50cccd1f1496571ac5c1f91386ac02c4d55516eeda19a24"}, + {file = "coverage-7.16.2-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dbbd1155ca46e6e0b6b89d204428c56ef6a459af21333f365d135a2820e5a09"}, + {file = "coverage-7.16.2-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8fc15cc8d0d06e873c00ef18e1372d605f9aaf3de27d8c24e50782e75bc8b843"}, + {file = "coverage-7.16.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c6afdd69218202bc1758c9a14b86b8cf1084f37ed2ca143e567a103772b16d1"}, + {file = "coverage-7.16.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aba5c63b7afdc749cc9eae943d5b868cba2b261a176378fa1c5a30bc8bc89982"}, + {file = "coverage-7.16.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9174f0af24e5eff248b9dbfe76ec5275a3d19d37edbc2810543f12cf97347a34"}, + {file = "coverage-7.16.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:80e9fdb4c3d926b6ba721d4bf7435bdb869c3527ae7803290361d0ab73db13b6"}, + {file = "coverage-7.16.2-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:7b3bce4a0d05401d70b7d0d5ca783e686bc9d30e81dbd7d980d532609bf809e4"}, + {file = "coverage-7.16.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:44f21e407b278efdfc1ee5e481e00518bd1d500310a30a5fbf2bcbedfef4aaf0"}, + {file = "coverage-7.16.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:59c3926585e1cd1f2190f4b2ac9014de1bbeaf0d5d0587b0dc6b0aa90d17896a"}, + {file = "coverage-7.16.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:066429634299e14dd2d511e1e85f8f9cecc500781f6b41907c0dd6f1baea7e63"}, + {file = "coverage-7.16.2-cp315-cp315-win32.whl", hash = "sha256:893ea9cf86cb8d2546812ac93d973aaf2ee1fb45110a873b014214fd23e3725e"}, + {file = "coverage-7.16.2-cp315-cp315-win_amd64.whl", hash = "sha256:01c6908bc613b420c26c818fe948e1b97dfd041a53c98b01c63bd8321f5c9aae"}, + {file = "coverage-7.16.2-cp315-cp315-win_arm64.whl", hash = "sha256:967d72c835d7a8cf0af99ec813a2d06e3db6df706402f1fe85b31b437645f495"}, + {file = "coverage-7.16.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:98d9c97f51b334b0adce7b964442a9af33c1a00c6ac856984cc5dc8d18f81c75"}, + {file = "coverage-7.16.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:3e861f1071dcc2fec1e88bef0920f6b1eaa66a143555b4f8ab79ba2b0f30ef55"}, + {file = "coverage-7.16.2-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb9d92ecfe2d5b494367c67f7446f8b75b68d8d0c8cf3bc3e6997478be25d9e2"}, + {file = "coverage-7.16.2-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb57acff4a74246ae513c142d4b36e18c389c3aed8661914a53f7cd0071031b2"}, + {file = "coverage-7.16.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:444889f7f66b74e4455c0a97e0e166dd41177f1dca8c0239a47cff25e05ba7e1"}, + {file = "coverage-7.16.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a740ea6f083c6db7b926534d159508f80ba275ab35e722522de0d18d0f56e55f"}, + {file = "coverage-7.16.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e209591f7c41ae4a9171335cf6156afda0b21de73b02f73f5aa95b2d5fbb08d"}, + {file = "coverage-7.16.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:396bb16e04ce04efbb3df91456ae4e3da918e69ecdf67fb711b0a0fdf35ccce0"}, + {file = "coverage-7.16.2-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9cdf19874e0d247f32f03609200370343c3c7aa260b191d8c2bb251d36198283"}, + {file = "coverage-7.16.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:fd3d72233eb8b48acc94fa57d44e2d32ce8e7abed02882ccb6d855ccc4ed33ec"}, + {file = "coverage-7.16.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:bb4ffe96aa663cee727659db5a2afeb38c95f8677b747d447b90d6d4874ea2c5"}, + {file = "coverage-7.16.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dba2edfb054f6d4a08df9d1637c39a5aa3865bca6617c13c86be21e45658a59c"}, + {file = "coverage-7.16.2-cp315-cp315t-win32.whl", hash = "sha256:251aed777c47c77aba047096d4542889db089227655711dfc2b9c54ef0e15e35"}, + {file = "coverage-7.16.2-cp315-cp315t-win_amd64.whl", hash = "sha256:2aca0bdfa9e91621d5b09d815357bf63def4fc0e9cb66da67bf2cf93f3b1a6f5"}, + {file = "coverage-7.16.2-cp315-cp315t-win_arm64.whl", hash = "sha256:b88841e654f09732804809e435b3e005a929ffd9998b872b7b213957b8759cb8"}, + {file = "coverage-7.16.2-py3-none-any.whl", hash = "sha256:11d28e9123a9156cb405d8d27b44256c9a58fb5decc2073a8f17862057e3aa0f"}, + {file = "coverage-7.16.2.tar.gz", hash = "sha256:ca64d9f1f384f151b9511bec01126072acd2f313439f8ed015a22d8790aab6fa"}, ] [package.extras] @@ -713,128 +713,140 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "rpds-py" -version = "2026.6.3" +version = "2026.9.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, - {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3"}, - {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da"}, - {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4"}, - {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6"}, - {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93"}, - {file = "rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a"}, - {file = "rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127"}, - {file = "rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804"}, - {file = "rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0"}, - {file = "rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a"}, - {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4"}, - {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa"}, - {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc"}, - {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822"}, - {file = "rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed"}, - {file = "rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f"}, - {file = "rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96"}, - {file = "rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223"}, - {file = "rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885"}, - {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4"}, - {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7"}, - {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d"}, - {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97"}, - {file = "rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0"}, - {file = "rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80"}, - {file = "rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb"}, - {file = "rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e"}, - {file = "rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc"}, - {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77"}, - {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698"}, - {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd"}, - {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d"}, - {file = "rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8"}, - {file = "rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5"}, - {file = "rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e"}, - {file = "rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2"}, - {file = "rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13"}, - {file = "rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a"}, - {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868"}, - {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187"}, - {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107"}, - {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba"}, - {file = "rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369"}, - {file = "rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146"}, - {file = "rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577"}, - {file = "rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76"}, - {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826"}, - {file = "rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4"}, + {file = "rpds_py-2026.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2"}, + {file = "rpds_py-2026.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e"}, + {file = "rpds_py-2026.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391"}, + {file = "rpds_py-2026.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win32.whl", hash = "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f"}, + {file = "rpds_py-2026.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12"}, + {file = "rpds_py-2026.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af"}, + {file = "rpds_py-2026.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e"}, + {file = "rpds_py-2026.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc"}, + {file = "rpds_py-2026.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win32.whl", hash = "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f"}, + {file = "rpds_py-2026.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02"}, + {file = "rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9"}, + {file = "rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33"}, + {file = "rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36"}, + {file = "rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9"}, + {file = "rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f"}, + {file = "rpds_py-2026.9.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b"}, + {file = "rpds_py-2026.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35"}, + {file = "rpds_py-2026.9.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785"}, + {file = "rpds_py-2026.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win32.whl", hash = "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c"}, + {file = "rpds_py-2026.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-win32.whl", hash = "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569"}, + {file = "rpds_py-2026.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e"}, + {file = "rpds_py-2026.9.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9"}, + {file = "rpds_py-2026.9.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297"}, + {file = "rpds_py-2026.9.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6"}, + {file = "rpds_py-2026.9.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win32.whl", hash = "sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15"}, + {file = "rpds_py-2026.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-win32.whl", hash = "sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b"}, + {file = "rpds_py-2026.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_10_12_x86_64.whl", hash = "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32"}, + {file = "rpds_py-2026.9.1-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_i686.whl", hash = "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_10_12_x86_64.whl", hash = "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0"}, + {file = "rpds_py-2026.9.1-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37"}, + {file = "rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12"}, ] [[package]] diff --git a/tests/types/ocpp/ChargingProfile.test.ts b/tests/types/ocpp/ChargingProfile.test.ts new file mode 100644 index 00000000..b40e99dd --- /dev/null +++ b/tests/types/ocpp/ChargingProfile.test.ts @@ -0,0 +1,35 @@ +/** + * @file Tests for cross-version charging-profile type guards + * @description Verifies that extension properties cannot change a profile's protocol classification + */ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + isOCPP16ChargingProfile, + OCPP20ChargingProfileKindEnumType, + OCPP20ChargingProfilePurposeEnumType, + type OCPP20ChargingProfileType, + OCPP20ChargingRateUnitEnumType, +} from '../../../src/types/index.js' + +await describe('ChargingProfile type guards', async () => { + await it('should not classify an OCPP 2.0 profile with an extension chargingProfileId as OCPP 1.6', () => { + const chargingProfile: OCPP20ChargingProfileType = { + chargingProfileId: 42, + chargingProfileKind: OCPP20ChargingProfileKindEnumType.Absolute, + chargingProfilePurpose: OCPP20ChargingProfilePurposeEnumType.TxProfile, + chargingSchedule: [ + { + chargingRateUnit: OCPP20ChargingRateUnitEnumType.W, + chargingSchedulePeriod: [{ limit: 11_000, startPeriod: 0 }], + id: 1, + }, + ], + id: 1, + stackLevel: 0, + } + + assert.strictEqual(isOCPP16ChargingProfile(chargingProfile), false) + }) +}) diff --git a/tests/types/ocpp/MeterValues.test.ts b/tests/types/ocpp/MeterValues.test.ts new file mode 100644 index 00000000..fe0d2739 --- /dev/null +++ b/tests/types/ocpp/MeterValues.test.ts @@ -0,0 +1,24 @@ +/** + * @file Tests for cross-version MeterValue type guards + * @description Verifies that protocol-specific guards remain disjoint at runtime + */ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + isOCPP16MeterValue, + isOCPP20MeterValue, + type MeterValue, +} from '../../../src/types/index.js' + +await describe('MeterValue type guards', async () => { + await it('should classify an empty sampled-value list only as OCPP 1.6', () => { + const meterValue: MeterValue = { + sampledValue: [], + timestamp: new Date(), + } + + assert.strictEqual(isOCPP16MeterValue(meterValue), true) + assert.strictEqual(isOCPP20MeterValue(meterValue), false) + }) +}) diff --git a/tests/utils/Configuration.test.ts b/tests/utils/Configuration.test.ts index ef245514..5226fad0 100644 --- a/tests/utils/Configuration.test.ts +++ b/tests/utils/Configuration.test.ts @@ -186,7 +186,7 @@ await describe('Configuration', async () => { await it('should throw for unknown configuration section', () => { assert.throws(() => { - Configuration.getConfigurationSection('unknown' as ConfigurationSection) + Configuration.getConfigurationSection('unknown' as unknown as ConfigurationSection) }, Error) }) }) diff --git a/tests/utils/ConfigurationUtils.test.ts b/tests/utils/ConfigurationUtils.test.ts index ce6d38c6..a28b42c7 100644 --- a/tests/utils/ConfigurationUtils.test.ts +++ b/tests/utils/ConfigurationUtils.test.ts @@ -41,7 +41,7 @@ await describe('ConfigurationUtils', async () => { // Test unsupported storage type assert.throws(() => { - getDefaultPerformanceStorageUri('unsupported' as StorageType) + getDefaultPerformanceStorageUri('unsupported' as unknown as StorageType) }, Error) }) }) diff --git a/ui/cli/src/commands/ocpp.ts b/ui/cli/src/commands/ocpp.ts index 6ab5a406..9f808606 100644 --- a/ui/cli/src/commands/ocpp.ts +++ b/ui/cli/src/commands/ocpp.ts @@ -2,8 +2,10 @@ import { Command, Option } from 'commander' import { buildAuthorizePayload, buildStatusNotificationPayload, - type ChargePointStatus, - type OCPP16ChargePointErrorCode, + getEnumStringValue, + OCPP16ChargePointErrorCode, + OCPP16ChargePointStatus, + OCPP20ConnectorStatusEnumType, OCPPVersion, ProcedureName, type RequestPayload, @@ -191,16 +193,25 @@ export const createOcppCommands = (program: Command): Command => { if (ocppVersion === OCPPVersion.VERSION_16 && options.errorCode == null) { throw new Error('--error-code is required for OCPP 1.6 stations') } + const status = + ocppVersion === OCPPVersion.VERSION_16 + ? getEnumStringValue(OCPP16ChargePointStatus, options.status) + : getEnumStringValue(OCPP20ConnectorStatusEnumType, options.status) + if (status == null) { + throw new Error(`Unsupported connector status '${options.status}'`) + } + const errorCode = + options.errorCode == null + ? undefined + : getEnumStringValue(OCPP16ChargePointErrorCode, options.errorCode) + if (options.errorCode != null && errorCode == null) { + throw new Error(`Unsupported OCPP 1.6 error code '${options.errorCode}'`) + } payload = { - ...buildStatusNotificationPayload( - options.connectorId, - options.status as ChargePointStatus, - ocppVersion, - { - errorCode: options.errorCode as OCPP16ChargePointErrorCode | undefined, - evseId: options.evseId, - } - ), + ...buildStatusNotificationPayload(options.connectorId, status, ocppVersion, { + errorCode, + evseId: options.evseId, + }), ...buildHashIdsPayload(resolvedHashIds), } await runAction(program, ProcedureName.STATUS_NOTIFICATION, payload, undefined, config) diff --git a/ui/common/src/client/adapter.ts b/ui/common/src/client/adapter.ts index 737c2679..0f5d84c2 100644 --- a/ui/common/src/client/adapter.ts +++ b/ui/common/src/client/adapter.ts @@ -1,6 +1,6 @@ -import type { DataConverter, WebSocketReadyState } from './types.js' +import type { DataConverter } from './types.js' -import { type WebSocketLike } from './types.js' +import { type WebSocketLike, WebSocketReadyState } from './types.js' interface RawWebSocket { close(code?: number, reason?: string): void @@ -86,7 +86,18 @@ export const createWsAdapter = (ws: RawWebSocket, options: WsAdapterOptions): We }, get readyState (): WebSocketReadyState { - return ws.readyState + switch (ws.readyState) { + case 0: + return WebSocketReadyState.CONNECTING + case 1: + return WebSocketReadyState.OPEN + case 2: + return WebSocketReadyState.CLOSING + case 3: + return WebSocketReadyState.CLOSED + default: + throw new RangeError(`Unknown WebSocket ready state: ${ws.readyState.toString()}`) + } }, send (data: string): void { diff --git a/ui/common/src/index.ts b/ui/common/src/index.ts index 944db50a..c2e35fb7 100644 --- a/ui/common/src/index.ts +++ b/ui/common/src/index.ts @@ -11,6 +11,7 @@ export * from './types/JsonType.js' export * from './types/UIProtocol.js' export * from './types/UUID.js' export * from './utils/converters.js' +export * from './utils/enum.js' export * from './utils/payloadBuilders.js' export * from './utils/UUID.js' export * from './utils/websocket.js' diff --git a/ui/common/src/utils/enum.ts b/ui/common/src/utils/enum.ts new file mode 100644 index 00000000..f2f48ef5 --- /dev/null +++ b/ui/common/src/utils/enum.ts @@ -0,0 +1,10 @@ +export const getEnumStringValue = ( + enumObject: Readonly>, + value: unknown +): T | undefined => { + for (const key in enumObject) { + if (Object.hasOwn(enumObject, key) && enumObject[key] === value) { + return enumObject[key] + } + } +} diff --git a/ui/web/package.json b/ui/web/package.json index 687d42f6..3cecdd24 100644 --- a/ui/web/package.json +++ b/ui/web/package.json @@ -46,6 +46,6 @@ "jsdom": "^30.1.1", "vite": "^8.3.1", "vitest": "^5.0.3", - "vue-tsc": "^3.3.11" + "vue-tsc": "^3.3.12" } } diff --git a/ui/web/src/skins/classic/ClassicLayout.vue b/ui/web/src/skins/classic/ClassicLayout.vue index b9140d6c..60fadc0c 100644 --- a/ui/web/src/skins/classic/ClassicLayout.vue +++ b/ui/web/src/skins/classic/ClassicLayout.vue @@ -34,16 +34,8 @@ Add Charging Stations @@ -154,6 +146,14 @@ const $chargingStations = useChargingStations() const $route = useRoute() const $router = useRouter() +const showChargingStations = (): void => { + $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS }).catch(() => undefined) +} + +const showAddChargingStations = (): void => { + $router.push({ name: ROUTE_NAMES.ADD_CHARGING_STATIONS }).catch(() => undefined) +} + const { activeSkinId, availableSkins: skins, switchSkin } = useSkin() const { activeThemeId, availableThemes, switchTheme } = useTheme() diff --git a/ui/web/src/skins/classic/components/actions/StartTransaction.vue b/ui/web/src/skins/classic/components/actions/StartTransaction.vue index 6b5720a9..f7c08683 100644 --- a/ui/web/src/skins/classic/components/actions/StartTransaction.vue +++ b/ui/web/src/skins/classic/components/actions/StartTransaction.vue @@ -37,8 +37,7 @@