]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
chore(deps-dev): apply updates main
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 4 Jul 2025 20:58:28 +0000 (22:58 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 4 Jul 2025 20:58:28 +0000 (22:58 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
233 files changed:
.cfignore
.dockerignore [new file with mode: 0644]
.eslintignore [deleted file]
.eslintrc.cjs [deleted file]
.github/ISSUE_TEMPLATE/bug_report.yml
.github/ISSUE_TEMPLATE/feature_request.yml
.github/dependabot.yml
.github/pull_request_template.md [new file with mode: 0644]
.github/release-please/config.json [new file with mode: 0644]
.github/release-please/manifest.json [new file with mode: 0644]
.github/workflows/autofix.yml [new file with mode: 0644]
.github/workflows/ci.yml
.github/workflows/clone-count.yml
.github/workflows/codeql-analysis.yml
.github/workflows/combine-prs.yml
.github/workflows/release-please.yml [new file with mode: 0644]
.husky/commit-msg
.husky/pre-commit
.lintstagedrc.js
.npmrc
.prettierrc.json
.release-it.json [deleted file]
.reuse/dep5 [deleted file]
.vscode/extensions.json
.vscode/settings.json
CHANGELOG.md
CODEOWNERS [new file with mode: 0644]
CONTRIBUTING.md
README.md
REUSE.toml [new file with mode: 0644]
docker/Dockerfile
docker/autoconfig.sh
docker/config.json
docker/docker-compose.yml
docker/run.sh [new file with mode: 0755]
docker/start.sh [deleted file]
e-mobility-charging-stations-simulator.code-workspace
eslint.config.js [new file with mode: 0644]
mikro-orm.config-template.ts
package.json
pnpm-lock.yaml
scripts/build-requirements.js [moved from build-requirements.js with 67% similarity]
scripts/bundle.js [moved from bundle.js with 85% similarity]
scripts/prepare.js [moved from prepare.js with 68% similarity]
scripts/runtime.js [new file with mode: 0644]
scripts/skip-preinstall.js [new file with mode: 0644]
sea-config.json [new file with mode: 0644]
skip-preinstall.js [deleted file]
sonar-project.properties
src/assets/ui-protocol/Insomnia-CSSimulatorUIHTTPProtocol.json
src/assets/ui-protocol/Insomnia-CSSimulatorUIWSProtocol.json
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationWorker.ts
src/charging-station/ConfigurationKeyUtils.ts
src/charging-station/Helpers.ts
src/charging-station/IdTagsCache.ts
src/charging-station/SharedLRUCache.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/broadcast-channel/UIServiceWorkerBroadcastChannel.ts
src/charging-station/broadcast-channel/WorkerBroadcastChannel.ts
src/charging-station/index.ts
src/charging-station/ocpp/1.6/OCPP16Constants.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20Constants.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPConstants.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ocpp/index.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIHttpServer.ts
src/charging-station/ui-server/UIServerFactory.ts
src/charging-station/ui-server/UIServerUtils.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/charging-station/ui-server/ui-services/UIService001.ts
src/charging-station/ui-server/ui-services/UIServiceFactory.ts
src/exception/OCPPError.ts
src/performance/PerformanceStatistics.ts
src/performance/storage/JsonFileStorage.ts
src/performance/storage/MikroOrmStorage.ts
src/performance/storage/MongoDBStorage.ts
src/performance/storage/None.ts
src/performance/storage/Storage.ts
src/performance/storage/StorageFactory.ts
src/scripts/deleteChargingStations.cjs
src/scripts/setCSPublicFlag.cjs
src/types/AutomaticTransactionGenerator.ts
src/types/ChargingStationConfiguration.ts
src/types/ChargingStationEvents.ts
src/types/ChargingStationInfo.ts
src/types/ChargingStationOcppConfiguration.ts
src/types/ChargingStationTemplate.ts
src/types/ChargingStationWorker.ts
src/types/ConfigurationData.ts
src/types/ConnectorStatus.ts
src/types/Error.ts
src/types/Evse.ts
src/types/FileType.ts
src/types/JsonType.ts
src/types/MapStringifyFormat.ts
src/types/MeasurandPerPhaseSampledValueTemplates.ts
src/types/MeasurandValues.ts
src/types/SimulatorState.ts
src/types/Statistics.ts
src/types/Storage.ts
src/types/UIProtocol.ts
src/types/WebSocket.ts
src/types/WorkerBroadcastChannel.ts
src/types/index.ts
src/types/ocpp/1.6/ChargePointErrorCode.ts
src/types/ocpp/1.6/ChargePointStatus.ts
src/types/ocpp/1.6/ChargingProfile.ts
src/types/ocpp/1.6/Configuration.ts
src/types/ocpp/1.6/DiagnosticsStatus.ts
src/types/ocpp/1.6/MeterValues.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/1.6/Transaction.ts
src/types/ocpp/2.0/Common.ts
src/types/ocpp/2.0/Requests.ts
src/types/ocpp/2.0/Responses.ts
src/types/ocpp/2.0/Variables.ts
src/types/ocpp/ChargePointErrorCode.ts
src/types/ocpp/ChargingProfile.ts
src/types/ocpp/Common.ts
src/types/ocpp/Configuration.ts
src/types/ocpp/ConnectorStatusEnum.ts
src/types/ocpp/ErrorType.ts
src/types/ocpp/MessageType.ts
src/types/ocpp/MeterValues.ts
src/types/ocpp/OCPPProtocol.ts
src/types/ocpp/OCPPVersion.ts
src/types/ocpp/Requests.ts
src/types/ocpp/Reservation.ts
src/types/ocpp/Responses.ts
src/types/ocpp/Transaction.ts
src/types/orm/entities/PerformanceRecord.ts
src/utils/AsyncLock.ts
src/utils/ChargingStationConfigurationUtils.ts
src/utils/Configuration.ts
src/utils/ConfigurationUtils.ts
src/utils/Constants.ts
src/utils/ElectricUtils.ts
src/utils/ErrorUtils.ts
src/utils/FileUtils.ts
src/utils/Logger.ts
src/utils/MessageChannelUtils.ts
src/utils/StatisticUtils.ts
src/utils/Utils.ts
src/utils/index.ts
src/worker/WorkerAbstract.ts
src/worker/WorkerConstants.ts
src/worker/WorkerDynamicPool.ts
src/worker/WorkerFactory.ts
src/worker/WorkerFixedPool.ts
src/worker/WorkerSet.ts
src/worker/WorkerTypes.ts
src/worker/WorkerUtils.ts
src/worker/index.ts
tests/charging-station/Helpers.test.ts [new file with mode: 0644]
tests/exception/BaseError.test.ts
tests/exception/OCPPError.test.ts [new file with mode: 0644]
tests/ocpp-server/.editorconfig [new file with mode: 0644]
tests/ocpp-server/.gitignore [new file with mode: 0644]
tests/ocpp-server/.lintstagedrc.js [new file with mode: 0644]
tests/ocpp-server/.vscode/extensions.json [new file with mode: 0644]
tests/ocpp-server/.vscode/settings.json [new file with mode: 0644]
tests/ocpp-server/CHANGELOG.md [new file with mode: 0644]
tests/ocpp-server/README.md [new file with mode: 0644]
tests/ocpp-server/__init__.py [new file with mode: 0644]
tests/ocpp-server/poetry.lock [new file with mode: 0644]
tests/ocpp-server/pyproject.toml [new file with mode: 0644]
tests/ocpp-server/server.py [new file with mode: 0644]
tests/ocpp-server/timer.py [new file with mode: 0644]
tests/types/ConfigurationData.test.ts [new file with mode: 0644]
tests/utils/AsyncLock.test.ts [new file with mode: 0644]
tests/utils/ConfigurationUtils.test.ts [new file with mode: 0644]
tests/utils/ElectricUtils.test.ts
tests/utils/ErrorUtils.test.ts [new file with mode: 0644]
tests/utils/StatisticUtils.test.ts
tests/utils/Utils.test.ts
tsconfig.json
ui/web/.eslintrc.cjs [deleted file]
ui/web/.lintstagedrc.js
ui/web/.prettierrc.json
ui/web/CHANGELOG.md [new file with mode: 0644]
ui/web/README.md
ui/web/docker/Dockerfile [new file with mode: 0644]
ui/web/docker/Makefile [new file with mode: 0644]
ui/web/docker/autoconfig.sh [new file with mode: 0755]
ui/web/docker/config.json [new file with mode: 0644]
ui/web/docker/docker-compose.yml [new file with mode: 0644]
ui/web/docker/run.sh [new file with mode: 0755]
ui/web/package.json
ui/web/sonar-project.properties
ui/web/src/components/Container.vue
ui/web/src/components/actions/AddChargingStations.vue
ui/web/src/components/actions/SetSupervisionUrl.vue
ui/web/src/components/actions/StartTransaction.vue
ui/web/src/components/buttons/Button.vue
ui/web/src/components/buttons/ToggleButton.vue
ui/web/src/components/charging-stations/CSConnector.vue
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/components/charging-stations/CSTable.vue
ui/web/src/composables/UIClient.ts
ui/web/src/composables/Utils.ts
ui/web/src/composables/index.ts
ui/web/src/main.ts
ui/web/src/router/index.ts
ui/web/src/shims-vue.d.ts
ui/web/src/types/ChargingStationType.ts
ui/web/src/types/ConfigurationType.ts
ui/web/src/types/JsonType.ts
ui/web/src/types/UIProtocol.ts
ui/web/src/types/index.ts
ui/web/src/views/ChargingStationsView.vue
ui/web/src/views/NotFoundView.vue
ui/web/start.js
ui/web/tests/unit/CSTable.spec.ts
ui/web/tsconfig.json
ui/web/vite.config.ts
ui/web/vitest.config.ts

index 33ccb0daa721bc88aafa5495db231604a0ca26fd..bd5a5aa424051f0b51a1385423dd2fdef96a5854 100644 (file)
--- a/.cfignore
+++ b/.cfignore
@@ -5,12 +5,7 @@
 .github
 .prettierrc.json
 .prettierignore
-.jshintrc
-.eslintrc.json
-.eslintrc.js
-.eslintrc.cjs
-.eslintrc.mjs
-.eslintignore
+eslint.config.js
 .eslintcache
 .lintstagedrc.json
 .lintstagedrc.js
@@ -26,7 +21,6 @@
 .nycrc.json
 .nyc_output
 .reuse
-.release-it.json
 .xmake.cfg
 # Logs
 logs/
diff --git a/.dockerignore b/.dockerignore
new file mode 100644 (file)
index 0000000..1b0b41c
--- /dev/null
@@ -0,0 +1,4 @@
+node_modules
+logs
+dist
+coverage
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644 (file)
index 5374793..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-dist/
-ui/web/
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
deleted file mode 100644 (file)
index d4ff8a2..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-// eslint-disable-next-line n/no-unpublished-require
-const { defineConfig } = require('eslint-define-config')
-
-module.exports = defineConfig({
-  root: true,
-  env: {
-    es2022: true,
-    node: true
-  },
-  parserOptions: {
-    sourceType: 'module',
-    ecmaVersion: 2022
-  },
-  plugins: ['simple-import-sort'],
-  extends: ['eslint:recommended', 'plugin:import/recommended'],
-  settings: {
-    'import/resolver': {
-      typescript: {
-        project: './tsconfig.json'
-      }
-    }
-  },
-  rules: {
-    'simple-import-sort/imports': 'error',
-    'simple-import-sort/exports': 'error'
-  },
-  overrides: [
-    {
-      files: ['**/*.ts'],
-      parser: '@typescript-eslint/parser',
-      parserOptions: {
-        project: './tsconfig.json'
-      },
-      plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc'],
-      extends: [
-        'plugin:@typescript-eslint/strict-type-checked',
-        'plugin:@typescript-eslint/stylistic-type-checked',
-        'plugin:import/typescript',
-        'love'
-      ],
-      rules: {
-        'operator-linebreak': 'off',
-        'tsdoc/syntax': 'warn'
-      }
-    },
-    {
-      files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
-      plugins: ['jsdoc'],
-      extends: ['plugin:n/recommended', 'plugin:jsdoc/recommended', 'standard'],
-      rules: {
-        'n/shebang': 'off'
-      }
-    }
-  ]
-})
index b0efef9b871e1adc37e7cd957fc77e77aff48b1a..d7f5e1b0ef0290b06c58c5fd232f625a414f22b1 100644 (file)
@@ -10,6 +10,7 @@ body:
 
         This issue tracker is for bugs and issues found in e-mobility-charging-stations-simulator.
         Any misuse of this issue tracker will be closed immediately, such as simulator configuration support, simulator usage support, ...
+        Support requests are expected to be filed in the [Discussions](https://github.com/poolifier/poolifier/discussions) Q&A category.
 
         Please fill in as much of the template below as you're able.
   - type: checkboxes
@@ -25,6 +26,7 @@ body:
       options:
         - Simulator
         - Web UI
+        - OCPP 2 Mock Server
     validations:
       required: true
   - type: textarea
index 0cee693fca2f14f700db8314dddec1f9470de383..272a5ff516484856bd5ec5f5ba50392ac45ff35c 100644 (file)
@@ -10,6 +10,7 @@ body:
 
         This issue tracker is for feature requests in e-mobility-charging-stations-simulator.
         Any misuse of this issue tracker will be closed immediately, such as simulator configuration support, simulator usage support, ...
+        Support requests are expected to be filed in the [Discussions](https://github.com/poolifier/poolifier/discussions) Q&A category.
 
         Please fill in as much of the template below as you're able.
   - type: checkboxes
@@ -25,6 +26,7 @@ body:
       options:
         - Simulator
         - Web UI
+        - OCPP 2 Mock Server
     validations:
       required: true
   - type: textarea
index eca1f15217266f9bf9014357f125fd9209c2e469..b86035e070f2079debc823f23c82502e7eb6492d 100644 (file)
@@ -1,22 +1,69 @@
 # Docs: https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates
 version: 2
 updates:
-  - package-ecosystem: 'github-actions'
+  - package-ecosystem: github-actions
     directory: '/'
     schedule:
-      interval: 'daily'
+      interval: daily
     labels:
       - 'github-actions'
-    reviewers:
-      - 'jerome-benoit'
-      - 'olivierbagot'
-  - package-ecosystem: 'npm'
+  - package-ecosystem: npm
     directory: '/'
     schedule:
-      interval: 'daily'
+      interval: daily
+    groups:
+      regular:
+        update-types:
+          - 'patch'
+          - 'minor'
+        exclude-patterns:
+          - 'typescript'
+      typescript:
+        update-types:
+          - 'patch'
+          - 'minor'
+          - 'major'
+        patterns:
+          - 'typescript'
+      eslint:
+        update-types:
+          - 'major'
+        patterns:
+          - 'eslint'
+          - '@eslint/*'
+      mikro-orm:
+        patterns:
+          - '@mikro-orm/*'
+      commitlint:
+        patterns:
+          - '@commitlint/*'
+      vitest:
+        patterns:
+          - 'vitest'
+          - '@vitest/*'
+      vite:
+        patterns:
+          - 'vite'
+          - '@vitejs/*'
     labels:
       - 'dependencies'
-    reviewers:
-      - 'jerome-benoit'
-      - 'olivierbagot'
     versioning-strategy: increase
+  - package-ecosystem: pip
+    directory: 'tests/ocpp-server'
+    schedule:
+      interval: daily
+    groups:
+      regular:
+        update-types:
+          - 'patch'
+          - 'minor'
+    labels:
+      - 'dependencies'
+  - package-ecosystem: docker
+    directories:
+      - '/docker'
+      - '/ui/web/docker'
+    schedule:
+      interval: daily
+    labels:
+      - 'docker'
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644 (file)
index 0000000..edef710
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+  Thanks for contributing to EVSE simulator project.
+  Please be sure to read our [contributing guidelines](https://github.com/sap/e-mobility-charging-stations-simulator/blob/master/CONTRIBUTING.md).
+-->
+
+## PR Checklist
+
+- [ ] Please add a description of your changes.
+- [ ] Please ensure title follows [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
+- [ ] We need your changes to come with unit tests in order to keep this project in quality and easy to maintain.
+- [ ] If your changes contain any new feature please be sure to document it.
+- [ ] Please add a link to the open issue or task that this pull request will resolve.
+
+## Does this PR introduce a breaking change?
+
+- [ ] Yes
+- [ ] No
+
+If yes please indicate it in the title and add migration info into the documentation.
+
+---
+
+<!-- Your PR text -->
diff --git a/.github/release-please/config.json b/.github/release-please/config.json
new file mode 100644 (file)
index 0000000..73df0e2
--- /dev/null
@@ -0,0 +1,43 @@
+{
+  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
+  "release-type": "node",
+  "bump-minor-pre-major": true,
+  "bump-patch-for-minor-pre-major": true,
+  "tag-separator": "@",
+  "include-v-in-tag": true,
+  "packages": {
+    ".": {
+      "exclude-paths": ["ui/web", "tests/ocpp-server"],
+      "component": "simulator",
+      "extra-files": ["sonar-project.properties"]
+    },
+    "ui/web": {
+      "component": "webui",
+      "extra-files": ["sonar-project.properties"]
+    },
+    "tests/ocpp-server": {
+      "release-type": "python",
+      "component": "ocpp-server"
+    }
+  },
+  "plugins": [
+    {
+      "type": "linked-versions",
+      "groupName": "simulator-ui-ocpp-server",
+      "components": ["simulator", "webui", "ocpp-server"]
+    }
+  ],
+  "changelog-sections": [
+    { "type": "feat", "section": "🚀 Features", "hidden": false },
+    { "type": "fix", "section": "🐞 Bug Fixes", "hidden": false },
+    { "type": "perf", "section": "⚡ Performance", "hidden": false },
+    { "type": "refactor", "section": "✨ Polish", "hidden": false },
+    { "type": "test", "section": "🧪 Tests", "hidden": false },
+    { "type": "docs", "section": "📚 Documentation", "hidden": false },
+
+    { "type": "build", "section": "🤖 Automation", "hidden": false },
+    { "type": "ci", "section": "🤖 Automation", "hidden": true },
+
+    { "type": "chore", "section": "🧹 Chores", "hidden": true }
+  ]
+}
diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json
new file mode 100644 (file)
index 0000000..bf09dec
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  ".": "2.0.10",
+  "ui/web": "2.0.10",
+  "tests/ocpp-server": "2.0.10"
+}
diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
new file mode 100644 (file)
index 0000000..c959098
--- /dev/null
@@ -0,0 +1,40 @@
+name: autofix.ci
+on:
+  push:
+  pull_request:
+    branches: [main]
+permissions:
+  contents: read
+
+jobs:
+  autofix:
+    runs-on: ubuntu-latest
+    continue-on-error: true
+    steps:
+      - uses: actions/checkout@v4
+      - run: pipx install poetry
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.13
+          cache: poetry
+
+      - working-directory: tests/ocpp-server
+        run: |
+          poetry install --no-root
+          poetry run task format
+
+      - uses: pnpm/action-setup@v4
+      - uses: actions/setup-node@v4
+        with:
+          cache: 'pnpm'
+
+      - run: pnpm install --ignore-scripts
+
+      - run: pnpm format
+
+      - working-directory: ui/web
+        run: |
+          pnpm format
+          pnpm lint:fix
+
+      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
index 9d6b5f136f7c53f951149bc3a27af5d9b7760976..e466708c55e497abdfab92170e4acb95ca94dfa6 100644 (file)
@@ -21,12 +21,36 @@ jobs:
           else
             echo "defined=false" >> $GITHUB_OUTPUT;
           fi
+  build-ocpp-server:
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        python: [3.12, 3.13]
+    name: Build OCPP mock server with Python ${{ matrix.python }} on ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    defaults:
+      run:
+        working-directory: tests/ocpp-server
+    steps:
+      - uses: actions/checkout@v4
+      - name: Install Poetry
+        run: pipx install poetry
+      - name: Setup Python ${{ matrix.python }}
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python }}
+          cache: poetry
+      - name: Install Dependencies
+        run: poetry install --no-root
+      - name: Lint
+        if: ${{ matrix.os == 'ubuntu-latest' && matrix.python == '3.13' }}
+        run: poetry run task lint
   build-simulator:
     needs: [check-secrets]
     strategy:
       matrix:
         os: [windows-latest, macos-latest, ubuntu-latest]
-        node: ['20.x', 'latest']
+        node: ['20.x', '22.x', '24.x', 'latest']
     name: Build simulator with Node ${{ matrix.node }} on ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
@@ -34,13 +58,13 @@ jobs:
         with:
           fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
       - name: Dependency Review
-        if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         uses: actions/dependency-review-action@v4
         with:
           base-ref: ${{ github.ref_name }}
           head-ref: ${{ github.sha }}
       - name: Pull Request Dependency Review
-        if: ${{ github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         uses: actions/dependency-review-action@v4
       - uses: pnpm/action-setup@v4
       - name: Setup node ${{ matrix.node }}
@@ -50,22 +74,22 @@ jobs:
           cache: 'pnpm'
       - name: pnpm install
         run: pnpm install --ignore-scripts --frozen-lockfile
-      - name: pnpm audit
-        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
-        run: pnpm audit --prod
+      - name: pnpm audit
+      #   if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
+        run: pnpm audit --prod
       - name: pnpm lint
-        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         run: pnpm lint
       - name: pnpm build
         run: pnpm build
       - name: pnpm test
         run: pnpm test
       - name: pnpm coverage
-        if: ${{ github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         run: pnpm coverage
       - name: SonarCloud Scan
-        if: ${{ needs.check-secrets.outputs.sonar-token-exists == 'true' && github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
-        uses: sonarsource/sonarcloud-github-action@v2.1.1
+        if: ${{ needs.check-secrets.outputs.sonar-token-exists == 'true' && github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
+        uses: sonarsource/sonarqube-scan-action@v5.2.0
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -74,7 +98,7 @@ jobs:
     strategy:
       matrix:
         os: [windows-latest, macos-latest, ubuntu-latest]
-        node: ['18.x', '20.x', 'latest']
+        node: ['20.x', '22.x', '24.x', 'latest']
     name: Build dashboard with Node ${{ matrix.node }} on ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     defaults:
@@ -85,13 +109,13 @@ jobs:
         with:
           fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
       - name: Dependency Review
-        if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         uses: actions/dependency-review-action@v4
         with:
           base-ref: ${{ github.ref_name }}
           head-ref: ${{ github.sha }}
       - name: Pull Request Dependency Review
-        if: ${{ github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         uses: actions/dependency-review-action@v4
       - uses: pnpm/action-setup@v4
       - name: Setup node ${{ matrix.node }}
@@ -101,30 +125,45 @@ jobs:
           cache: 'pnpm'
       - name: pnpm install
         run: pnpm install --ignore-scripts --frozen-lockfile
-      - name: pnpm audit
-        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
-        run: pnpm audit --prod
+      - name: pnpm audit
+      #   if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
+        run: pnpm audit --prod
       - name: pnpm lint
-        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         run: pnpm lint
       - name: pnpm build
         run: pnpm build
       - name: pnpm test
         run: pnpm test
       - name: pnpm coverage
-        if: ${{ github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
+        if: ${{ github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
         run: pnpm coverage
       - name: SonarCloud Scan
-        if: ${{ needs.check-secrets.outputs.sonar-token-exists == 'true' && github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
-        uses: sonarsource/sonarcloud-github-action@v2.1.1
+        if: ${{ needs.check-secrets.outputs.sonar-token-exists == 'true' && github.repository == 'sap/e-mobility-charging-stations-simulator' && matrix.os == 'ubuntu-latest' && matrix.node == '22.x' }}
+        uses: sonarsource/sonarqube-scan-action@v5.2.0
         with:
           projectBaseDir: ui/web
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-  build-docker-image:
+  build-simulator-docker-image:
     runs-on: ubuntu-latest
-    name: Build docker image
+    name: Build simulator docker image
+    steps:
+      - uses: actions/checkout@v4
+      - name: Setup Docker Buildx
+        id: buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Build docker image
+        run: |
+          cd docker
+          make SUBMODULES_INIT=false
+  build-dashboard-docker-image:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ui/web
+    name: Build dashboard docker image
     steps:
       - uses: actions/checkout@v4
       - name: Setup Docker Buildx
index 43119339ba527a8b35fc0f5bc05c43523ba56d8c..4d68216c2347bc4763228ce2813020d140e05c49 100644 (file)
@@ -8,6 +8,7 @@ on:
 jobs:
   clone-count:
     runs-on: ubuntu-latest
+    if: github.repository == 'sap/e-mobility-charging-stations-simulator'
 
     steps:
       - uses: actions/checkout@v4
index ca6b410f53637b1f6c2cdbf1a1caa097759b41ef..ea8716bcf72fe14f210d276b1e88bf4fb4a09b29 100644 (file)
@@ -17,8 +17,6 @@ on:
   pull_request:
     # The branches below must be a subset of the branches above
     branches: [main]
-  # merge_group:
-  #   branches: [main]
   schedule:
     - cron: '17 15 * * 1'
 
index 06fdf4dc8eb931d9f4248ba7d393c7fcf1f64888..8f55579771185c3bbed17aeeef0e0651af8c08b8 100644 (file)
@@ -17,6 +17,6 @@ jobs:
     steps:
       - name: combine-prs
         id: combine-prs
-        uses: github/combine-prs@v5.0.0
+        uses: github/combine-prs@v5.2.0
         with:
-          github_token: ${{ secrets.SECRET_TOKEN }}
+          github_token: ${{ secrets.WORKFLOW_TOKEN }}
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644 (file)
index 0000000..f3fff5e
--- /dev/null
@@ -0,0 +1,38 @@
+name: Release Please
+
+on:
+  push:
+    branches:
+      - main
+
+permissions:
+  contents: write
+  pull-requests: write
+
+jobs:
+  release-please:
+    runs-on: ubuntu-latest
+    if: github.repository == 'sap/e-mobility-charging-stations-simulator'
+    steps:
+      - uses: googleapis/release-please-action@v4
+        id: release
+        with:
+          token: ${{ secrets.WORKFLOW_TOKEN }}
+          config-file: .github/release-please/config.json
+          manifest-file: .github/release-please/manifest.json
+      - uses: actions/checkout@v4
+        if: ${{ steps.release.outputs.release_created }}
+      - name: Create tags for major and minor versions
+        if: ${{ steps.release.outputs.release_created }}
+        run: |
+          git config user.name github-actions[bot]
+          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
+          git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
+          git tag -d v${{ steps.release.outputs.major }} || true
+          git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
+          git push origin :v${{ steps.release.outputs.major }} || true
+          git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
+          git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}"
+          git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}"
+          git push origin v${{ steps.release.outputs.major }}
+          git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}
index 0a4b97de53ae4f83db224a73981e6c0aaf581203..dbce4f4cf4b518c5c7c82f69a8b17d18ee6b526e 100755 (executable)
@@ -1 +1 @@
-npx --no -- commitlint --edit $1
+commitlint --edit $1
index 041c660c92b36e939248b12a0fb99f203d210be2..c27d8893a99490cc432461db991faf1b276e005f 100755 (executable)
@@ -1 +1 @@
-npx --no-install lint-staged
+lint-staged
index 0124480b4544144624cc6959bfd0a8ff223cee57..79297cee0a9030c10b911a3011d673ebfa8329d7 100644 (file)
@@ -1,5 +1,5 @@
 export default {
-  '{src,tests}/**/*.{ts,tsx,cts,mts}': ['prettier --cache --write', 'eslint --cache --fix'],
+  '**/*.{js,jsx,cjs,mjs}': ['prettier --cache --write', 'eslint --cache --fix'],
   '**/*.{json,md,yml,yaml}': ['prettier --cache --write'],
-  '**/*.{js,jsx,cjs,mjs}': ['prettier --cache --write', 'eslint --cache --fix']
+  '{src,tests}/**/*.{ts,tsx,cts,mts}': ['prettier --cache --write', 'eslint --cache --fix'],
 }
diff --git a/.npmrc b/.npmrc
index 9b852ce8e2bf5cda6df176d411ccdd7cd15f5734..7260c73e708ec4a09149ff557011c83a7f653a2f 100644 (file)
--- a/.npmrc
+++ b/.npmrc
@@ -1,3 +1,3 @@
-ignore-workspace-root-check=true
+@jsr:registry=https://npm.jsr.io
 auto-install-peers=true
 legacy-peer-deps=true
index 066e89fcd2212b26dc027ef6abfb85510c9bae0f..b962a2dd7a7a404be05bec53d5e449f7cba2f72c 100644 (file)
@@ -4,5 +4,5 @@
   "arrowParens": "avoid",
   "singleQuote": true,
   "semi": false,
-  "trailingComma": "none"
+  "trailingComma": "es5"
 }
diff --git a/.release-it.json b/.release-it.json
deleted file mode 100644 (file)
index 850d67a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "git": {
-    "requireBranch": "main",
-    "changelog": "pnpm exec auto-changelog --stdout --commit-limit false --unreleased --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs",
-    "commitMessage": "chore: version ${version}",
-    "tagAnnotation": "Version ${version}",
-    "tagExclude": "*[-]*",
-    "pushRepo": "origin"
-  },
-  "npm": {
-    "publish": false
-  },
-  "github": {
-    "release": true,
-    "releaseName": "Version ${version}"
-  },
-  "plugins": {
-    "@release-it/bumper": {
-      "out": ["sonar-project.properties"]
-    }
-  },
-  "hooks": {
-    "after:bump": "pnpm exec auto-changelog --commit-limit false --package --template https://raw.githubusercontent.com/release-it/release-it/master/templates/keepachangelog.hbs"
-  }
-}
diff --git a/.reuse/dep5 b/.reuse/dep5
deleted file mode 100644 (file)
index 71534a6..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: e-mobility-charging-stations-simulator
-Upstream-Contact: Jerome Benoit <jerome.benoit@sap.com>
-Source: <https://github.com/sap/e-mobility-charging-stations-simulator>
-Disclaimer: The code in this project may include calls to APIs ("API Calls") of
- SAP or third-party products or services developed outside of this project
- ("External Products").
- "APIs" means application programming interfaces, as well as their respective
- specifications and implementing code that allows software to communicate with
- other software.
- API Calls to External Products are not licensed under the open source license
- that governs this project. The use of such API Calls and related External
- Products are subject to applicable additional agreements with the relevant
- provider of the External Products. In no event shall the open source license
- that governs this project grant any rights in or to any External Products,or
- alter, expand or supersede any terms of the applicable additional agreements.
- If you have a valid license agreement with SAP for the use of a particular SAP
- External Product, then you may make use of any API Calls included in this
- project's code for that SAP External Product, subject to the terms of such
- license agreement. If you do not have a valid license agreement for the use of
- a particular SAP External Product, then you may only make use of any API Calls
- in this project for that SAP External Product for your internal, non-productive
- and non-commercial test and evaluation of such API Calls. Nothing herein grants
- you any rights to use or access any SAP External Product, or provide any third
- parties the right to use of access any SAP External Product, through API Calls.
-
-Files: *
-Copyright: 2021-2024 SAP SE or an SAP affiliate company and e-mobility-charging-stations-simulator contributors
-License: Apache-2.0
index e28460d3105bcd28e57846775a9deedfbd180c90..06a422a220e28f24d4b306743b1ceb6007817276 100644 (file)
@@ -5,7 +5,7 @@
     "connor4312.nodejs-testing",
     "dbaeumer.vscode-eslint",
     "EditorConfig.EditorConfig",
-    "ms-azuretools.vscode-docker",
+    "ms-vscode-remote.remote-containers",
     "sonarsource.sonarlint-vscode",
     "streetsidesoftware.code-spell-checker"
   ]
index adb17d15ef744ec390624a47d166930504a0dda2..7ca8bd7339ebe9a0026f597268def6e247f9f0f4 100644 (file)
     "bufferutil",
     "cacheable",
     "CENTI",
+    "chargingstations",
     "choco",
     "commitlint",
     "corepack",
     "csms",
+    "ctrlr",
     "DECI",
     "doctorprof",
-    "emerg",
     "emobility",
     "evlink",
     "evse",
     "idtags",
     "imsi",
     "keba",
+    "kvar",
+    "kvarh",
     "lcov",
-    "linebreak",
     "logform",
     "measurand",
     "measurands",
     "mikro",
     "MILLI",
     "mnemonist",
+    "neostandard",
     "ocpp",
     "olivierbagot",
     "onconnection",
     "parens",
     "piment",
     "poolifier",
+    "postject",
     "preinstall",
-    "rambda",
-    "Recurrency",
+    "recurrency",
     "RFID",
     "shutdowning",
     "sonarlint",
     "SRPC",
-    "tsdoc",
+    "varh",
     "VCAP",
     "webui",
+    "workerd",
     "workerset"
   ]
 }
index e3a03ebf77991da0573fc6d2cbde496f465c8e03..3ca76f84c22739089fc31ab7e956f6cd94ea0470 100644 (file)
@@ -1,6 +1,719 @@
 # Changelog
 
-## [v1.3.3](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.2...v1.3.3)
+## [2.0.10](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.9...simulator@v2.0.10) (2025-07-03)
+
+### ⚡ Performance
+
+- speed up mean and median computation ([0c58e0c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0c58e0c202eafd13fc96ea40b045e8ce9f119e52))
+
+### ✨ Polish
+
+- cleanup empty data structure checks ([3cc104c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3cc104c22939ea5c126faf04543745f7773b5548))
+- cleanup statistics related namespace ([034962b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/034962b82c0eb94cc3f1df604e95343ad4facc87))
+- handle empty string configuration file name ([c820074](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c820074de27fab5f31b371c80614f0de6361b3f1))
+
+### 🤖 Automation
+
+- **deps:** bump the regular group across 1 directory with 6 updates ([#1452](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1452)) ([b039a4f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b039a4fc9382aec82b63e48b126a47cb615f7dd4))
+
+## [2.0.9](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.8...simulator@v2.0.9) (2025-06-27)
+
+### 🐞 Bug Fixes
+
+- check vendorId against CS template at DataTransfer cmd handling ([#1450](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1450)) ([d39adbd](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d39adbd45dfc5fe4a42d64b93d229cfdfad878a9))
+
+### ✨ Polish
+
+- refine type definitions ([13b0cc5](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/13b0cc5ceee4c372d64ed21c1fd783407fd119ca))
+
+### 🧪 Tests
+
+- add UTs for newly added helpers ([06ca76a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/06ca76ae5f156e86591bb1f21c80240ff8dd956f))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node from 22.15.31 to 24.0.0 ([#1437](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1437)) ([b871758](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b871758ddaa7e28b479d45bf7585f172b4db36c4))
+- **deps-dev:** bump the regular group with 2 updates ([#1444](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1444)) ([dcd454c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dcd454c166a2afcc25f992d3cb7c0fb6f7a07284))
+- **deps-dev:** bump the regular group with 3 updates ([3b34fab](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3b34fabb6b659eadbf2ec96936851d69e43e043d))
+- **deps-dev:** bump the regular group with 3 updates ([#1427](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1427)) ([9f10bb4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9f10bb49f617f45d0d14c58bac901b7e0d96bc2b))
+- **deps-dev:** bump the vite group with 3 updates ([1cd81ff](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1cd81ff001b7505ddbdd9260bc5485367ac8973a))
+- **deps:** bump autofix-ci/action ([#1434](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1434)) ([25b538c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/25b538c6eb451adcad220aba48f66e9ef6d16619))
+
+## [2.0.8](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.7...simulator@v2.0.8) (2025-05-27)
+
+### 🐞 Bug Fixes
+
+- **docker:** dependencies installation with latest pnpm ([b1dab0c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b1dab0ce2c231c96a3ba1aecb559054879745170))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @cspell/eslint-plugin from 8.19.4 to 9.0.0 ([#1408](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1408)) ([78c7a82](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/78c7a82d33b0f3a9dc9713e4631b0dbdc652b193))
+- **deps-dev:** bump lint-staged from 15.5.2 to 16.0.0 ([#1413](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1413)) ([f6d19a9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f6d19a9dd3a420f7e2d9beeaffcddfe90ba73de5))
+- **deps:** bump sonarsource/sonarqube-scan-action from 5.1.0 to 5.2.0 ([7a64458](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7a64458c37ea67ff15cf0723501c56fff73a56cb))
+- **deps:** bump the regular group across 1 directory with 15 updates ([c74bb2c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c74bb2c6d158b4344ef8acfc6a8d6ae84cbc0801))
+- **deps:** bump the regular group across 1 directory with 9 updates ([#1411](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1411)) ([384bf4b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/384bf4b7a95fbc8d93b17950b4c7988982ea648a))
+
+## [2.0.7](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.6...simulator@v2.0.7) (2025-04-30)
+
+### 🐞 Bug Fixes
+
+- ensure buffered messages flushing is stopped when WS is closed ([4e0e653](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4e0e6536ce7de6a83e9343e6873d3054b58f2532))
+- throttle failed buffered messages sending with exponential backoff ([#1399](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1399)) ([f43dbe1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f43dbe14bc4075c3fa6ec7611d8cab7885db69d6))
+
+### ✨ Polish
+
+- refine log level in buffered OCPP messages handling ([84fa762](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/84fa762257648a5d6f2095251b722563d201193d))
+
+### 📚 Documentation
+
+- recommend container tools vscode extension ([7282b15](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7282b156ff929492e067eebe326b748e3bcc80a3))
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group across 1 directory with 5 updates ([#1396](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1396)) ([637b90c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/637b90ccf2a4c187e5681ab37d394e4cf57dd5bf))
+- **deps-dev:** bump vite from 6.2.5 to 6.2.6 in the regular group ([#1382](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1382)) ([c369454](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c369454c86a86543f38ce9549d16ac57a7b4e360))
+- **deps-dev:** bump vite from 6.3.3 to 6.3.4 in the regular group ([#1400](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1400)) ([3ea8331](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3ea83318991587b84a8d7df00655f437ef170752))
+- **deps:** bump the regular group across 1 directory with 7 updates ([#1393](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1393)) ([e55d6c4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e55d6c40ca642e2dfb9f764a2b824a4eaba38494))
+
+## [2.0.6](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.5...simulator@v2.0.6) (2025-04-08)
+
+### 🐞 Bug Fixes
+
+- ensure the BootNotification interval is reused a registration ([fcc3ada](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fcc3ada9f33a04267d32fac032eee1bd0ad98152))
+- gracefully handle malformed boot notification response ([db7e450](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/db7e450e595dae12f32520fe3d1a7f1061c3616f))
+
+## [2.0.5](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.4...simulator@v2.0.5) (2025-04-08)
+
+### 🐞 Bug Fixes
+
+- ensure CS in pending state can send boot notification ([#1376](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1376)) ([b72d174](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b72d174972edbd0572e0489036924934e6d8ea1c))
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group with 2 updates ([1b0518d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1b0518d781c3c2f5fb08243c5e8b39cbe8da871e))
+- **deps-dev:** bump the regular group with 2 updates ([a86ad89](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a86ad89b37158d888826946197e82e28afa51606))
+- **deps:** bump the regular group with 7 updates ([33f5c72](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/33f5c724c277263dcb35d82dfb97e6d31f627175))
+
+## [2.0.4](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.3...simulator@v2.0.4) (2025-04-01)
+
+### 🐞 Bug Fixes
+
+- properly handle empty key array in GetConfiguration ([#1368](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1368)) ([d13819a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d13819a6bfadb0a08c6c2379f5a4bdb4000be4eb)), closes [#1364](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1364)
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group with 6 updates ([da28b39](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/da28b394d059de0fd2cfa28bafe037e382411f7e))
+- **deps:** bump mongodb from 6.14.2 to 6.15.0 in the regular group ([#1354](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1354)) ([dbbcc02](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dbbcc0206c58011165e816787efc8351193f3bcb))
+- **deps:** bump sonarsource/sonarqube-scan-action from 5.0.0 to 5.1.0 ([961c27d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/961c27d882f267bced5516081b3ddcbd157c1ca1))
+- **deps:** bump the regular group with 6 updates ([#1357](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1357)) ([93c3165](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/93c3165637b688b851ac9de682e9fcc8cc4a6d00))
+
+## [2.0.3](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.2...simulator@v2.0.3) (2025-03-17)
+
+### 🐞 Bug Fixes
+
+- **docker:** workaround corepack bug ([88a04b7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/88a04b71ccf51cf4f94a279c95e072f0306d902b))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node in the regular group ([#1264](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1264)) ([7d3076d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7d3076df490d0c4fe2f19bba39066384bf2a6060))
+- **deps-dev:** bump @types/node in the regular group ([#1269](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1269)) ([f76119c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f76119cefac3d3ef7f8186e5e37e92c11f58be5f))
+- **deps-dev:** bump @types/node in the regular group ([#1317](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1317)) ([cb49916](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cb49916d261d2785d0fe96cdaf674e3fc6a29469))
+- **deps-dev:** bump eslint-plugin-perfectionist in the regular group ([#1267](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1267)) ([a5946ce](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a5946ce92ecb10e34b4f3d9141411c465bf5d7ab))
+- **deps-dev:** bump eslint-plugin-perfectionist in the regular group ([#1271](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1271)) ([ba78676](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ba7867687bbf53d9b966d8995706348c048c6973))
+- **deps-dev:** bump eslint-plugin-vue from 9.32.0 to 9.33.0 ([56dc680](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/56dc680ad901f68459ba04bbb27ad37cc5c35264))
+- **deps-dev:** bump glob from 11.0.0 to 11.0.1 in the regular group ([#1274](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1274)) ([df397f6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/df397f603cec1aa565474ac0d01edb7c064fdb1f))
+- **deps-dev:** bump jsdom from 25.0.1 to 26.0.0 ([#1276](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1276)) ([a082525](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a082525de20451c572433ac5cb9b4b6c0a908b1d))
+- **deps-dev:** bump the regular group across 1 directory with 2 updates ([#1349](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1349)) ([b038814](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b038814501c9bf3b83ad59afe1b92d76c520c887))
+- **deps-dev:** bump the regular group across 1 directory with 6 updates ([#1300](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1300)) ([4ec2ae9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4ec2ae9d1a25c3e7605fa329a59a55b9b30708ca))
+- **deps-dev:** bump the regular group with 2 updates ([#1278](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1278)) ([fa27c74](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fa27c744c34e0c70a92741acef570bdefc65c3be))
+- **deps-dev:** bump the regular group with 2 updates ([#1279](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1279)) ([84f9a57](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/84f9a5783bc76c46acf6a31c29f16f698096f318))
+- **deps-dev:** bump the regular group with 2 updates ([#1288](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1288)) ([1598cd2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1598cd2b66f5ee794b3de24c3cddb7df27e058f9))
+- **deps-dev:** bump the regular group with 3 updates ([#1292](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1292)) ([460b4f7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/460b4f787eaa9d6ec9ff928b5e674679ac069982))
+- **deps-dev:** bump the regular group with 3 updates ([#1316](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1316)) ([33f023c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/33f023c84ef127cd19d1034b0ca27dc54d18c30b))
+- **deps-dev:** bump the regular group with 5 updates ([#1347](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1347)) ([7de098e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7de098e773583a32d92bd41babf6fb7590f637ee))
+- **deps-dev:** bump typescript in the typescript group ([db6e518](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/db6e518be63b8eba2510937c9291ac34acbd9563))
+- **deps-dev:** bump typescript in the typescript group ([#1275](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1275)) ([8a16c2c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8a16c2cd8dea56de21d7a4bfabc2178b4091f6d8))
+- **deps-dev:** bump typescript in the typescript group ([#1336](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1336)) ([f59c2bc](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f59c2bc913c580258e8e574c6faa41cfbd0e2041))
+- **deps-dev:** bump vite from 5.4.11 to 5.4.12 ([#1293](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1293)) ([3935e7d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3935e7dcb8b4d46c43f3b73db8cbd4cab0b6aa00))
+- **deps-dev:** bump vite from 5.4.14 to 6.2.0 in the vite group ([03487e6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/03487e6ed10b9dccf45836dfc6f14e8e1ad53244))
+- **deps-dev:** bump vite from 6.2.1 to 6.2.2 in the regular group ([e406114](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e406114618fc4b9055eab801a4cca08b032683d7))
+- **deps:** bump autofix-ci/action ([#1284](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1284)) ([5c1e170](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5c1e17073424144c5b08dbc87f997036b1fb0618))
+- **deps:** bump finalhandler from 1.3.1 to 2.1.0 ([4c20ab7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4c20ab72a54def59e360353ebd2d82e8c4243129))
+- **deps:** bump mnemonist from 0.40.0-rc1 to 0.40.0 ([#1298](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1298)) ([b61b19b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b61b19b4fbd08c8efef9109529ce686c7ba74579))
+- **deps:** bump mongodb from 6.13.1 to 6.14.0 in the regular group ([e9ef4d3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e9ef4d390b75be3c28db31d8cdf003c2427cf443))
+- **deps:** bump sonarsource/sonarqube-scan-action from 4.2.1 to 5.0.0 ([02f1f13](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/02f1f13aadb84f94b1c33f573019eeef838b0c63))
+- **deps:** bump the regular group across 1 directory with 3 updates ([3c61d82](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3c61d827b67cda74fa4f0ff76d169a238f0ccf06))
+- **deps:** bump the regular group across 1 directory with 3 updates ([#1285](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1285)) ([1831ac9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1831ac9e3426d88f58523de957428a121fd423b8))
+- **deps:** bump the regular group across 1 directory with 3 updates ([#1337](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1337)) ([efb264d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/efb264d54f0e3d3d0c6a024215915e95779127f8))
+- **deps:** bump the regular group across 1 directory with 5 updates ([074d58e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/074d58e7718045e8cbd1535c19f435ec27ef9d51))
+- **deps:** bump the regular group across 1 directory with 6 updates ([#1305](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1305)) ([fea949e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fea949ef7ab1ad7d8b30e34470f62703a711c067))
+- **deps:** bump the regular group across 1 directory with 9 updates ([5dd44c7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5dd44c73f3c8a77069d71631fa93e2d7ff9cbe6e))
+- **deps:** bump the regular group with 10 updates ([#1353](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1353)) ([0a7a66b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0a7a66bc2a448d881f63c361a39e193b13a8c2a3))
+- **deps:** bump the regular group with 14 updates ([97693a9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/97693a9d2f0689847de045d4828cf3c6e1189d2e))
+- **deps:** bump the regular group with 2 updates ([a917716](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a917716e9de67dbc6211d394ebfc82eabdb008e3))
+- **deps:** bump the regular group with 2 updates ([#1262](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1262)) ([a8de006](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a8de0063cd6e9e817d8edf715679173c9beed470))
+- **deps:** bump the regular group with 5 updates ([dfca506](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dfca506c9ff6d5908eeb6a8941967137e3bb4574))
+- **deps:** bump the regular group with 5 updates ([#1273](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1273)) ([3594fbd](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3594fbd4ef832fb9da039f103ee6b2bc4e062863))
+- **deps:** bump the regular group with 6 updates ([#1302](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1302)) ([a12cb9b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a12cb9bc7d6130c48ec689e34d84c2efb32343fd))
+- **deps:** bump the regular group with 8 updates ([fd124fb](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fd124fbe6004358e03569ae44027bb9886063d9c))
+
+## [2.0.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.1...simulator@v2.0.2) (2024-12-23)
+
+### ✨ Polish
+
+- cleanup some OCPP type definitions ([11ca3c7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/11ca3c7f34134ea0db5cb3733c09b455acd80fd7))
+- deprecate incorrect error type entry ([8ae3581](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8ae35815daa6783872a6077f97acbcd4ad7e2899))
+
+### 📚 Documentation
+
+- add link for support request GH discussions category ([7eed01f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7eed01f7d48ed1f3212f10ca29df09c460de6f31)), closes [#1136](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1136)
+
+### 🤖 Automation
+
+- **deps-dev:** bump @cspell/eslint-plugin in the regular group ([#1233](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1233)) ([318f5d1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/318f5d1468ed701e2b5313fd715d6f991ef0b263))
+- **deps-dev:** bump eslint-plugin-perfectionist in the regular group ([#1242](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1242)) ([71060bb](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/71060bb2c71000868010fd2da403f2de67a7b029))
+- **deps-dev:** bump eslint-plugin-perfectionist in the regular group ([#1253](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1253)) ([8b8c282](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8b8c282316ff8803d9ed1cd8c086b798d9e3fd77))
+- **deps-dev:** bump prettier from 3.4.1 to 3.4.2 in the regular group ([#1243](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1243)) ([a1bbf37](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a1bbf3717246b87c33e935cdb28179eb78eae109))
+- **deps-dev:** bump the regular group with 2 updates ([#1235](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1235)) ([0414e23](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0414e23c28c3a44aea711d596516a21070a3b867))
+- **deps-dev:** bump the regular group with 2 updates ([#1250](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1250)) ([ac8c9b4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ac8c9b463f91ccedf33f39ab0dcb115df0fc6069))
+- **deps-dev:** bump the regular group with 2 updates ([#1258](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1258)) ([ad1ab8e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ad1ab8e54dee1b88b5c4700cc6d8c16a4d47dafb))
+- **deps-dev:** bump the regular group with 5 updates ([#1239](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1239)) ([5b34f7a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5b34f7ae2193fedb89219b108bbcc696303bd910))
+- **deps-dev:** bump the regular group with 6 updates ([#1230](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1230)) ([6ffb90e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6ffb90e3d27c1935dc65cf9517ecfec0bfe31053))
+- **deps:** bump autofix-ci/action ([#1241](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1241)) ([1c54315](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1c5431517bfa93aa1891efb8103f3edceb85e408))
+- **deps:** bump sonarsource/sonarcloud-github-action ([#1245](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1245)) ([4718131](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4718131cdc218fa807c1687b1547c385a20697f5))
+- **deps:** bump sonarsource/sonarqube-scan-action from 4.1.0 to 4.2.1 ([#1255](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1255)) ([6d60411](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6d604115cedb1496189db879029605b8b70f686a))
+- **deps:** bump the regular group with 2 updates ([#1228](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1228)) ([01ae0b2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/01ae0b21e24f7490f67c7f51434c03854123be58))
+- **deps:** bump the regular group with 4 updates ([#1251](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1251)) ([2cfd250](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2cfd2508ff99ea6600088b503e592ca0ef9a114c))
+- **deps:** bump the regular group with 5 updates ([#1249](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1249)) ([208b128](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/208b128397cb7cbc93c3d1d711be501659c643a3))
+- **deps:** bump the regular group with 5 updates ([#1254](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1254)) ([a01fdca](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a01fdca77f0394354ccd4310ff3dffb49adc1e1e))
+- **deps:** bump the regular group with 6 updates ([#1260](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1260)) ([b0ce06a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b0ce06ab9c9454d4600c45e83fb6fd7739647756))
+
+## [2.0.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v2.0.0...simulator@v2.0.1) (2024-11-22)
+
+### 🐞 Bug Fixes
+
+- ensure undefined is handled at computing power limitation ([55a17ee](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/55a17ee03b588b4cd5b14276b1f103bcdd3ec0a4)), closes [#1223](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1223)
+
+### ✨ Polish
+
+- cleanup linter warnings ([1ea7f1d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1ea7f1dfaa14dfb2be0855e73e3b541da86af536))
+- cleanup logger export ([a715340](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a71534044917af1e6d37f5ad9209f61b3b317f7b))
+- silence linter warnings ([fa8baf1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fa8baf177c5691312128077deb9299a44a7e2f42))
+- use micro tasks queue instead next tick one ([95e17f6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/95e17f676b7c1067ce40de914335e35afb862df8))
+
+### 🧪 Tests
+
+- fix CI failure ([87a4259](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/87a4259551eb51e3565b14343dbdf74cc97db343))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node in the regular group ([05eeba4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/05eeba4c543c89a098f89d2859922ecca92d81d3))
+- **deps-dev:** bump @types/node in the regular group ([#1206](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1206)) ([009b39e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/009b39e67fc3ed602b676daa8d7984fee62a45dc))
+- **deps-dev:** bump @types/node in the regular group ([#1207](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1207)) ([a8ecc38](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a8ecc3853c85790d59a67befa2d31e588624d29d))
+- **deps-dev:** bump eslint-plugin-jsdoc in the regular group ([#1216](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1216)) ([9369001](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/93690010cfed503d846e9df514059bfb29b2dc1b))
+- **deps-dev:** bump the regular group with 4 updates ([#1217](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1217)) ([639c849](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/639c849b2cf9fbc586f7b101896f7ed0cf61066e))
+- **deps:** bump mongodb from 6.10.0 to 6.11.0 in the regular group ([#1225](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1225)) ([087efff](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/087efff031eff90ad3fd7e161566754d4beb701b))
+- **deps:** bump the regular group across 1 directory with 2 updates ([#1203](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1203)) ([90eb3b3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/90eb3b34a3679829df20d3b008115d23444d1c1e))
+- **deps:** bump the regular group with 11 updates ([#1215](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1215)) ([1c3e200](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1c3e200fa957bae57aa33da1117828abd6d7b61e))
+- **deps:** bump the regular group with 2 updates ([#1212](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1212)) ([7c4f2ba](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7c4f2baeb2988e79e98a77a969f800c267c1fb44))
+- **deps:** bump the regular group with 2 updates ([#1219](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1219)) ([9b823a0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9b823a0faa557dadeb5ea951e76fb40ce875b553))
+- **deps:** bump the regular group with 5 updates ([#1205](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1205)) ([6e0ee99](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6e0ee990fee626e27d99103aad81ccbc9658fd63))
+- **deps:** bump the regular group with 6 updates ([#1211](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1211)) ([636421d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/636421d3912f65978bc31ab486d4d9f0f6e3cc3d))
+- fix linter errors ([408b4e6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/408b4e6d9b9a617ef4a1fc465e407e4711886b24))
+
+## [2.0.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.5.2...simulator@v2.0.0) (2024-10-23)
+
+### ⚠ BREAKING CHANGES
+
+- bump the minimum node version supported to 20.x.x
+
+### ⚡ Performance
+
+- use crypto `hash` instead of `createHash` ([1f9416c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1f9416ccaf3bacf0c4f3f61ae8501d8b3f036ad2))
+
+### 🤖 Automation
+
+- **ci:** readd pnpm audit step ([11d2ed9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/11d2ed98b089dff19a7c2521b0f36ffc81a49b0c))
+- **ci:** switch to workflow token ([ecb01bf](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ecb01bf1c1d631d2f0920c870097c7f17df6118a))
+- **deps-dev:** bump neostandard in the regular group ([#1198](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1198)) ([d65f40c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d65f40c07014ba5c72e180ec8ca43b67bac0aacf))
+- **deps:** bump the regular group with 2 updates ([#1197](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1197)) ([6f31a7a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6f31a7a48fbe760691ebc36695ca8ab07b538f8b))
+
+### 🧹 Chores
+
+- bump the minimum node version supported to 20.x.x ([7b8957c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7b8957c7a45de0415157f08ce7aa18e0b2fc83c4))
+
+## [1.5.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.5.1...simulator@v1.5.2) (2024-10-21)
+
+### 🐞 Bug Fixes
+
+- fix has() rambda usage ([dfffcac](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dfffcac323d3168250d029205981eb8d054cb463))
+- make sea script works on Windows™ ([844081b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/844081b0ccab6d76dcd269aeca9a4e3ae599c468))
+
+### ⚡ Performance
+
+- speed up isAsyncFunction() helper ([f8d74e9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f8d74e92b50b36d7e5cb9c4a8ecbfcfc8fee0cee))
+
+### ✨ Polish
+
+- cleanup blank lines ([b1421bc](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b1421bc351387bb5efd738f0be8cfd5ab94a4426))
+- cleanup comments formatting ([f46aabb](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f46aabbfb98e55c0b6d4bec15a2f2e9ba92a8e14))
+- **deps-dev:** remove unused deps ([fd855c1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fd855c1b97eb0eb50caba0f766c94b2cea4ff5e0))
+- **docker:** rename start.sh to run.sh ([baf8b16](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/baf8b16408ca3209ec4b9b8b07e1a9b026761444))
+- order ErrorType enum properly ([420b812](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/420b812b683f370ed611b3ef264909445c3e1917))
+- refine isNotEmptyString() and isNotEmptyArray() helpers type ([665864c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/665864cacd27d66cd323b6f66b2724f6e9e244e8))
+- remove unneeded blank lines ([b78c3f2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b78c3f238166ea6c763bf046704a476a8ada90f1))
+- revert overzealous enums ordering ([42f3318](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/42f331849ea30557749162cc37b0c4658933cf93))
+- separate out dashboard docker image ([20fb109](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/20fb10949dc2553d7695be1aab02bf84a8ddab98)), closes [#1040](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1040)
+- silence linter ([a4a246b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a4a246bbfaf265d6f91fb366c70e64a5ee84c43c))
+- switch to eslint-plugin-perfectionist ([0749233](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0749233f25516e4c73ee8dbcea8c4ad6b8a506bb))
+- turn on `noImplicitOverride` in TS configuration ([6375d3c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6375d3cdd7f42a6e125976df194f4fe689d24113))
+- use ENTRYPOINT syntax in docker files ([989108f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/989108f63bd531f04b28e65c8b499c9dbf5bbed0))
+- use rambda has() helper ([e0e6de7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e0e6de7ff7e4fad7f4b7ed364caba0a25f075a70))
+
+### 🧪 Tests
+
+- add AsyncLock test ([b842c65](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b842c65bd62d35d7b19f5d45371dfe0c6539e6d3))
+- add checkStationInfoConnectorStatus() test ([dd81d27](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dd81d27d043073971f29ec761261489f16c66541))
+- add getPhaseRotationValue() test ([2997179](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/29971799109b0dff8f12f0977b5f520ec55cc100))
+- add missing tests/utils/AsyncLock.test.ts file ([35f57b2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/35f57b2c14853801f09f675a08decf17f5f6da4c))
+- cleanup AsyncLock test ([213958d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/213958d630327ba513982a7f18642b09ec5a03c8))
+- fix simulator configuration test ([32f3b30](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/32f3b3045faf65cdfdf4ef7dba57e782b30b4430))
+- improve AsyncLock test coverage ([aff8ddb](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/aff8ddb379f1d6659ae6aac4d23acfecde6ceef9))
+- refine AsyncLock runExclusive() expectation ([bff96f3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bff96f3a942ee6ff5f85926683440945017ffeab))
+
+### 📚 Documentation
+
+- **CONTRIBUTING.md:** reference SAP GenAI contribution guidelines ([#1164](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1164)) ([0f41d83](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0f41d83d644491bbe4247caf886136e763986119))
+
+### 🤖 Automation
+
+- **ci:** comment out pnpm audit step ([f03694f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f03694f0645b67a40f97067eb11d6722f4897851))
+- **ci:** node 23.x support ([2244858](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/22448581a254e4ea149ad67a333d0694254dfeb0))
+- **ci:** run OCPP server build GHA on several OS ([1752bac](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1752bacff32da5e2e80ecf12f5239d6ccf256e49))
+- **ci:** silence linter errors ([3b7b2e3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3b7b2e36648e6af19111f3b065ff43b1dbe917aa))
+- **deps-dev:** bump @types/node from 22.0.3 to 22.1.0 ([#1120](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1120)) ([c11dc2a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c11dc2a0aca044ee03dc565dfaff7342b4ce039a))
+- **deps-dev:** bump @vitest/coverage-v8 from 2.0.4 to 2.0.5 ([#1117](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1117)) ([4ee8259](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4ee82592a066961b6877858ed29a825bfd480acf))
+- **deps-dev:** bump eslint-plugin-jsdoc in the major group ([#1131](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1131)) ([90c55af](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/90c55af4d09233be277d0e73a621441a564c0199))
+- **deps-dev:** bump eslint-plugin-jsdoc in the regular group ([#1172](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1172)) ([89fb871](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/89fb871535d9c6a4d0ca08894606369a60eed86f))
+- **deps-dev:** bump eslint-plugin-jsdoc in the regular group ([#1190](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1190)) ([1d26740](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1d267405713f36adaf4e3199328a735643d13630))
+- **deps-dev:** bump eslint-plugin-jsdoc in the regular group ([#1192](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1192)) ([2cba942](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2cba942ca12f2ea943a179cc40110445b4709e6e))
+- **deps-dev:** bump eslint-plugin-vue in the regular group ([#1195](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1195)) ([be79449](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/be7944967cff38f5afd10ee2aa20553e4284a538))
+- **deps-dev:** bump jsdom from 24.1.1 to 25.0.0 in the major group ([#1152](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1152)) ([bb7aeb1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bb7aeb1e64f53b1b3af1b1b33c405bb13310578c))
+- **deps-dev:** bump the regular group with 2 updates ([cc3e26b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cc3e26b41348dffd62937a9b44e4b22d963beebc))
+- **deps-dev:** bump the regular group with 2 updates ([#1138](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1138)) ([81fe15c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/81fe15c66400b48457ab34908cac32ca00d61e38))
+- **deps-dev:** bump the regular group with 2 updates ([#1156](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1156)) ([d366869](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d36686932f284482ecfd095387a717b92ce64b5a))
+- **deps-dev:** bump the regular group with 2 updates ([#1183](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1183)) ([9de6af7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9de6af7490dc616d685c474b0d54c19c53fb15cd))
+- **deps-dev:** bump the regular group with 3 updates ([#1167](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1167)) ([fcca987](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fcca98745121549db537d9ce588e38900c8f0e0d))
+- **deps-dev:** bump vitest from 2.0.4 to 2.0.5 ([#1116](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1116)) ([e454b55](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e454b5538856316f8492463262186a24e02e2925))
+- **deps:** bump github/combine-prs from 5.1.0 to 5.2.0 ([#1188](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1188)) ([99cca19](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/99cca19cec0e67601f2973a1a4c985fe3dac6417))
+- **deps:** bump rambda from 9.2.1 to 9.3.0 in the regular group ([#1150](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1150)) ([c862413](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c8624132fb742098c26e7a3a446ae7364ed5784a))
+- **deps:** bump sonarsource/sonarcloud-github-action ([d1b99e7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d1b99e7b824a6f0a20fc936b58ad8fa3eb7b2d7d))
+- **deps:** bump sonarsource/sonarcloud-github-action ([#1185](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1185)) ([9f7011d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9f7011df003d985b0dc4032fb83d53c6dc669f7a))
+- **deps:** bump the regular group across 1 directory with 12 updates ([#1140](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1140)) ([89e8682](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/89e8682d8e1b3936bd17af7eca0142f9ad695b7f))
+- **deps:** bump the regular group across 1 directory with 4 updates ([58e990c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/58e990c7169a44eaa5ee14029119713ce92f5033))
+- **deps:** bump the regular group with 10 updates ([#1153](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1153)) ([48bd33f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/48bd33f9432231b9551d65ceb5cd14b937e5cff1))
+- **deps:** bump the regular group with 2 updates ([#1129](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1129)) ([d9f951c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d9f951c258d12765106548688672b8413716fb16))
+- **deps:** bump the regular group with 2 updates ([#1143](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1143)) ([f883a83](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f883a83f647b54a7b625e19972e94224f768c50b))
+- **deps:** bump the regular group with 2 updates ([#1155](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1155)) ([c02ea07](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c02ea0790b61c5b29c353ff1297fa5e3fe5538da))
+- **deps:** bump the regular group with 2 updates ([#1165](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1165)) ([e1fb067](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e1fb067c6a2d00106c5bf7ec0707d5374fdca464))
+- **deps:** bump the regular group with 2 updates ([#1189](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1189)) ([d626254](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d626254db881732a6478258428fd37da26c4cabb))
+- **deps:** bump the regular group with 3 updates ([#1151](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1151)) ([271426f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/271426fb72220de7969c8200067f51793a5502a7))
+- **deps:** bump the regular group with 3 updates ([#1170](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1170)) ([fb89e15](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fb89e155dbfd8be508fa42a08fc44dad925bd7ad))
+- **deps:** bump the regular group with 4 updates ([#1157](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1157)) ([35fbcd3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/35fbcd383c244ae746b45c031abac4b6cbadf001))
+- **deps:** bump the regular group with 4 updates ([#1166](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1166)) ([2f2d625](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2f2d625b102d7211e5311c8367690bd03ce8087a))
+- **deps:** bump the regular group with 4 updates ([#1173](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1173)) ([b07a57e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b07a57e8de27faf8008b26e94f78978cc1b58ff4))
+- **deps:** bump the regular group with 5 updates ([#1128](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1128)) ([2786b45](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2786b45addc24eaefc167970ef42f831573cc7b8))
+- **deps:** bump the regular group with 5 updates ([#1187](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1187)) ([7c2dbe5](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7c2dbe52f8fb160bfd25090793e0b7f4d7df1317))
+- **deps:** bump the regular group with 5 updates ([#1191](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1191)) ([c9a4d44](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c9a4d44f8a45e92a7b5dd755c0b811c85f5c2f6e))
+- **deps:** bump the regular group with 6 updates ([#1163](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1163)) ([e6ea62c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e6ea62c8b4c1ba8c99c8502a9df24709f46e19cf))
+- **deps:** bump the regular group with 7 updates ([f5d5fe0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f5d5fe0e8449c8c1ecd560cdaea66757d4cb6168))
+- **deps:** bump the regular group with 7 updates ([#1130](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1130)) ([4f1d887](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4f1d8877c57dd59ec6b862c5caee613532e0873a))
+- **deps:** bump the regular group with 7 updates ([#1158](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1158)) ([4809da4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4809da42ba62306e05652e685aba604bc66ec179))
+- **deps:** bump the regular group with 7 updates ([#1168](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1168)) ([2336b18](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2336b18a25e03b086d23fd7c99753ac9201eee6e))
+- **deps:** bump the regular group with 7 updates ([#1182](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1182)) ([462fe86](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/462fe8611ff8b5938b6e9164f897210d475d3fb5))
+- **deps:** bump the regular group with 9 updates ([#1137](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1137)) ([eede99d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/eede99da5f4df39204a48daf6caf8463c1048be6))
+- **deps:** bump vue from 3.4.36 to 3.4.37 in the regular group ([#1132](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1132)) ([1e755e0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1e755e07ca8925d906b4b83a9a430d4d4dc4b31d))
+- **deps:** bump vue from 3.5.1 to 3.5.2 in the regular group ([54e7c79](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/54e7c7947381865de499ddff816d007399942040))
+- **deps:** bump vue from 3.5.10 to 3.5.11 in the regular group ([#1184](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1184)) ([b216885](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b216885bc638297def6a91f530813a4ab078dc5c))
+- **deps:** bump winston from 3.14.0 to 3.14.1 in the regular group ([#1135](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1135)) ([99edec9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/99edec9e2c356c875dc6c9a1f0aaca1840ab1af1))
+- **sea:** cleanup build artefacts ([2e65f18](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2e65f185a91dcce233c38fcd1e2383c68c017f02))
+- **sonar:** refine sonar-project.properties files ([216a56a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/216a56a2a3c4548eb9fdb3a67793e653b4f8bbf3))
+
+## [1.5.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.5.0...simulator@v1.5.1) (2024-07-30)
+
+### ✨ Polish
+
+- cleanup eslint configuration ([31dab31](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/31dab314ef8b2f785e8074218e1889792752049a))
+- move buidling, bundling, ... scripts into a directory ([bf30bb2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bf30bb229a6296e16bd4aab4cf7285f7dd187288))
+- silence two linter errors ([664ba1d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/664ba1dde7b64ea9f9a262a31dec9aa91c6a92f3))
+- **test:** code cleanups ([7edfbc1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7edfbc133ab8b7d617cbb1e4fbda4acd4c63800d))
+
+### 🧪 Tests
+
+- add checkConfiguration() helper test ([243a494](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/243a494b9598b7aa85f9b5cdca067cd5fd4f5b06))
+- add more charging station helpers tests ([e01619b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e01619ba83f630b55890f18e1adbc561ef95fd50))
+- add some tests for charging station helpers ([df59920](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/df5992062c1e0975e1e159f7fa7b81bcc263bd27))
+
+### 📚 Documentation
+
+- add OCPP 2 mock server component to issue templates ([a95db78](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a95db7832890d86af2eb75ffe39f3baad686eb09))
+
+### 🤖 Automation
+
+- **ci:** reenabled auto code formating and linting on simulator ([1e4b13c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1e4b13ce2da1be9ee2437dd057d477309168b716))
+- **ci:** reenabled linting on web ui code ([5a1832c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5a1832ce790d0398fa031e738db47eb94fa975ec))
+- **deps-dev:** bump @types/ws from 8.5.11 to 8.5.12 ([#1113](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1113)) ([1e10f26](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1e10f2640989a05281874950de452b76fc57d59a))
+- **deps-dev:** bump eslint-plugin-jsdoc from 48.8.3 to 48.9.2 ([#1112](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1112)) ([79e727f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/79e727f19f927ee2532ff95337a363b8ee594bbb))
+- **deps-dev:** bump eslint-plugin-jsdoc from 48.9.2 to 48.10.1 ([#1115](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1115)) ([9cd5283](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9cd5283ad66aeefacdcf3b7df7c8685e6df83d6b))
+- **deps-dev:** bump husky from 9.1.3 to 9.1.4 ([#1114](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1114)) ([c398be4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c398be430ad2d387e8982af00e54fc89e2b0db8e))
+
+## [1.5.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.4.2...simulator@v1.5.0) (2024-07-25)
+
+### 🚀 Features
+
+- add initial node sea support ([f6e18e3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f6e18e33b68a67239eba3f4d873172a491283d00))
+
+### 🐞 Bug Fixes
+
+- ensure OCPP payload validation report error details ([45087a8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/45087a81ca7dda0b6e72897acd1391d859026961))
+
+### ✨ Polish
+
+- npx -&gt; pnpm dlx where appropriates ([bf25e53](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bf25e531346a2e0579cb14d1d7c1da45dbb0aa08))
+- permit build code to run with other JS runtime ([456b7f2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/456b7f2d7fc934d306e948e906c847417432009b))
+- refine OCPP PDU validation error messages ([5827ccc](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5827ccc27d6c46f82df4e42a5707cfa19b550442))
+- refine station information validation error message ([915c88d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/915c88d593292e7a47c92868e4b2d3752cce4dcd))
+- validate station information at CS init ([b55f94b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b55f94b6ac5dee58f38d13781b4345c4cc43f349))
+
+### 🧪 Tests
+
+- fix clone() test ([53ec800](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/53ec800a644f89a299cede7a89dd8a7b05959797))
+
+### 📚 Documentation
+
+- **README.md:** refine volta installation on windows ([fad9e72](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fad9e7271b22c74fe6eb67e939090cf529a2b249))
+
+### 🤖 Automation
+
+- **ci:** fix autofix GH action ([c033310](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c03331039ff7a080a3456d5a88fb7b18d505df7f))
+- **deps-dev:** bump @types/node from 20.14.11 to 20.14.12 ([#1106](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1106)) ([fb46a87](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fb46a877f5c2b880759b9e70a08092510a7cb473))
+- **deps-dev:** bump @vitejs/plugin-vue from 5.0.5 to 5.1.0 ([#1105](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1105)) ([d36c4c8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d36c4c840ab5ac7a9c7fe6abbd2ae01844cb981e))
+- **deps-dev:** bump @vitest/coverage-v8 from 2.0.1 to 2.0.2 ([bbdb386](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bbdb3861ed14ea52669231e5d372add76c50d65e))
+- **deps-dev:** bump eslint-plugin-jsdoc from 48.5.2 to 48.6.0 ([fd091c8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fd091c8cd219780ff650859e7b4506180f436861))
+- **deps-dev:** bump glob from 10.4.3 to 11.0.0 ([cff23bd](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cff23bd9ab0f9ca92bc2d4721bf454b00a54daf0))
+- **deps-dev:** bump husky from 9.0.11 to 9.1.0 ([#1091](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1091)) ([e74759d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e74759d1c5cc66ef325b75bae39cc930ae33eb92))
+- **deps-dev:** bump rimraf from 5.0.8 to 6.0.0 ([207408c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/207408cf661a7b8a1d35f8a9aa5ff02104fcd8ba))
+- **deps-dev:** bump typescript-eslint from 7.16.0 to 7.16.1 ([a60a99d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a60a99dce05328466e9a631ba83a9d527f3b4548))
+- **deps-dev:** bump vitest from 2.0.1 to 2.0.2 ([#1082](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1082)) ([f3d0d30](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f3d0d30346974ec0e979d53d8a761ce254ba8bb0))
+- **deps:** bump github/combine-prs from 5.0.0 to 5.1.0 ([#1093](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1093)) ([55f4d38](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/55f4d386ab0e3903101c8a4b296b962cf71c9427))
+- **deps:** bump tar from 7.4.0 to 7.4.1 ([#1102](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1102)) ([0da233c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0da233c3b206d71bda0504908a1fd429547de923))
+- **deps:** bump vue from 3.4.33 to 3.4.34 ([#1107](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1107)) ([0bfef14](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0bfef1423a926bd6054a25934957148d1d2fb4b0))
+
+## [1.4.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.4.1...simulator@v1.4.2) (2024-07-06)
+
+### 📚 Documentation
+
+- format 'Branching model' section in README.md ([3196db2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3196db2fbe206231fc0ee7d7947b19937a23ce42))
+
+### 🤖 Automation
+
+- **ci:** do not cancel workflow in case of autofix failure ([7e302de](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7e302dea6f55838438fbe7bf0b9b8e70f3c3af21))
+- **ci:** fix release-please changelog after module renaming ([26058fd](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/26058fdb1d89d53caceceb1a981e3d48d73dcfae))
+- **ci:** rename module in changelog ([b7c140d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b7c140d7aad6d935d8941b07062011d4171596e9))
+- **deps:** bump poolifier from 4.0.15 to 4.0.16 ([8965c9e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8965c9e361bd771e59963c7ebd22b4d3fc0da04b))
+
+## [1.4.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator@v1.4.0...simulator@v1.4.1) (2024-07-05)
+
+### 🐞 Fixes
+
+- **ci:** fix release branches creation ([f727f02](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f727f029cb974cffb38a2c569173730b6b808e3f))
+
+### ✨ Polish
+
+- **ci:** cleanup release-please configuration ([9880e6f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9880e6fed0333e17aad03f33e5ec4ee6307510ec))
+
+## [1.4.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/simulator-v1.3.7...simulator@v1.4.0) (2024-07-04)
+
+### 🚀 Features
+
+- switch to release-please release manager ([2c02376](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2c02376b6b17a6094c451b976275ed9eb2e1407b))
+
+### 🐞 Fixes
+
+- **ci:** fix release-please GH action run ([2a91b70](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2a91b70f1cf33c64ee6f2f3be63bb9b5614cd319))
+- **ci:** silence release-please GH action warnings ([4dc1046](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4dc10464e640d30a12ece043edcd72390cd1fb43))
+- ensure checks are run on release PR ([ce4b669](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ce4b6698ed11fbee77caf6f448fef2db36240bcc))
+
+### 📚 Documentation
+
+- add branching model to README.md ([d295031](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d295031fa6c94271ec551f32bba55f9cd99c998e))
+- add PR template ([8d9bfb0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8d9bfb088bc280dac892ca71f40bb085842eb7e7))
+
+### ✨ Polish
+
+- **test:** code cleanups ([b53037c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b53037c097bf8412831281229234e814aaae82a9))
+- **test:** typing cleanups ([65a1581](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/65a1581afd90a44096f1c0157abf4d5531d76fa5))
+
+### 🤖 Automation
+
+- bump volta node version ([abc5de8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/abc5de857ea9e4a27f83d1c2fe60b4618775a540))
+- **ci:** fix component handling in release-please ([60414a7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/60414a7e313e6ce7b3a9effd8a8cccb41555a04c))
+- **ci:** limit initial release PR to previous version commit ([e49bac4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e49bac45f5d513b19991fde86e7c234eb46ab85d))
+- **ci:** remove release-please workspace plugin ([94097a8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/94097a8d456f51654a9b201e41309d9628638a81))
+- **deps-dev:** apply updates ([e92bd99](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e92bd991d1f0c43442dd20d7b5e34fd30f9bb136))
+- **deps-dev:** apply updates ([a33e3b4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a33e3b4129757990b84af075cf9b678506937afb))
+- **deps:** bump ws from 8.17.1 to 8.18.0 ([4119c6e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4119c6edbafb3fe813587770a2fb5a91c85a8340))
+
+## [v1.3.7](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.6...v1.3.7)
+
+- fix: fix OCPP2 mock server command sending [`#1061`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1061)
+- Combined PRs [`#1064`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1064)
+- Fix GetBaseReport Implementation and Add Request Handlers and CLI Parsing Server-Side [`#1053`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1053)
+- build(deps-dev): bump eslint-plugin-jsdoc from 48.2.12 to 48.4.0 [`#1059`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1059)
+- build(deps): bump sonarsource/sonarcloud-github-action from 2.2.0 to 2.3.0 [`#1048`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1048)
+- build(deps): bump actions/setup-python from 4 to 5 [`#1043`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1043)
+- Add GetBaseReport Command Handling to OCPP 2.0.1 Server [`#1041`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1041)
+- build(deps-dev): apply updates [`11846be`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/11846be1d14c97fd648b9492f861e1ee6f84f2c5)
+- build(deps-dev): apply updates [`2177f6d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/2177f6d759c2cacf2469d4c7b6de4f2caa7a08bc)
+- test: migrate ocpp server to poetry to ease usage [`c11be92`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c11be92a38d40495df5ec37b9ff946993c4dc84f)
+- build(deps-dev): apply updates [`9d31316`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/9d31316ec2ba72f89f2709088ba8482137efa697)
+- build(deps-dev): apply updates [`e6f14eb`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e6f14ebf2ecbe64a4f5d026632948c4a2f8b4d65)
+- build(deps): apply updates [`ede334c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ede334c888a1ffccb0ea420c1523e06120276df0)
+- refactor(ocpp-server): format code with black [`d4aa970`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d4aa970003b5e5a012dceaa33d1905a170cb461b)
+- build(deps-dev): apply updates [`7f6b56b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7f6b56b555843a45f040f5b709b98725f38bed16)
+- build(deps): apply updates [`06b6085`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/06b60859cbfedab83c209fb2ecdbe0537daf7dfb)
+- test(ocpp-server): switch to ruff to assess coding style [`dfa4a58`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/dfa4a5842974c6de70aa04e84a303f1d10b4bb8b)
+- build(deps): apply updates [`5ebea52`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5ebea52afe3ede727cb216e6708156738ced7e42)
+- build(deps-dev): apply updates [`6537ae8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6537ae8f2148496c326bbcd4fdf95dface14c8b8)
+- build(ci): silence linter [`4d8b5b9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4d8b5b9019b4853c97df80817dfb72e7f854b61a)
+- build(deps-dev): apply updates [`5ddbebe`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5ddbebe972f979292d0e258d766702aa0fc1eb85)
+- fix(ocpp-server): add asyncio compatible timer implementation and use it [`cbfbfbc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cbfbfbc11f32acd3da4ae64aaa558b506d0de131)
+- build(deps-dev): apply updates [`69497ad`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/69497ade5b8f692ea0fa785a2fae2059798c9d9f)
+- build(deps-dev): apply updates [`d4c5e39`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d4c5e39617edd8b9612d564ca693737381d356d9)
+- test: reformat ocpp server python using pep8 rules [`1a0d2c4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1a0d2c470d1b156381a9002155ffd6ff9a9f629d)
+- build(deps-dev): apply updates [`6c7388a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6c7388a4c4c47e9d19511b8e9b70b0f57d6dbd5f)
+- Add missing types for GetBaseReport payload handling [`fa16d38`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fa16d389b8eb171e581c3fe5789602c54ac85ab1)
+- build(deps-dev): apply updates [`9388120`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/9388120364d5a2f3b9103f05802e1756d2dad04f)
+- build(deps-dev): apply updates [`12c0935`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/12c0935b3d52b06957d0c80ef4d5e361ea3aafa3)
+- test(ocpp-server): add tasks to format/lint code [`ac96356`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ac96356cae880d404129e8cb56eeacdd712ea1d4)
+- fix: issue 39 [`299eb3f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/299eb3fafb21481e38e963ff6080d0822ae6a3ae)
+- refactor: setup repo configuration to handle python code [`d6488e8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d6488e8da9634892f480dd4323a64d87716cbf13)
+- refactor: cleanup default params in error handlers [`64c14c9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/64c14c99f9902d4e96f2a2cd404e536e0584a629)
+- build(deps-dev): apply updates [`a6a928b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a6a928b1e16fbc3537298c225829e29bf8eea12f)
+- feat: modified OCPP2.0 test [`7c945b4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7c945b4ac211aafb6e90645a0364c12d7373522e)
+- Related issue39 [`f937c17`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/f937c172eebbdfb2d90693806bc045e915bf828d)
+- build(deps-dev): apply updates [`7e7760d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7e7760d1bae2a948c88d008130714b2067fcc543)
+- build(deps-dev): apply updates [`a79c81a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a79c81afb8d0bc53368fcab7bb341f77ab9f84c0)
+- feat(ci): add autofix.ci action on PR [`a4beba2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a4beba2d2d81f4b122eceff72c2a495c27180011)
+- fix: modified mistakes [`b225460`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b22546014db62ca65e14ecaec6ea62d2c986a4b6)
+- refactor(ocpp-server): add a few types [`c7f80bf`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c7f80bf972e52fd1afa315ffdf02ff603f6b9e8b)
+- refactor: move more OCPP command sending code to charge point class [`4de4645`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4de4645dcdfbd3512247c7272888def66f71d30d)
+- fix(ocpp-server): properly handle charge point disconnection [`7628b7e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7628b7e63d130ddc602ed3647abf36e8ae35f6dd)
+- build(deps-dev): bump ruff from 0.4.10 to 0.5.0 in /tests/ocpp-server [`2b8b04b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/2b8b04bdc9838756c37fbe86a517a2b502c125ed)
+- build(ci): run autofix in a unique job [`8b17228`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8b17228e91d57af035488d1939a07666e5d8649a)
+- [autofix.ci] apply automated fixes [`b794f42`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b794f42e9d8f01fc21654ed4452df45a79f5f871)
+- build(deps-dev): apply updates [`0f1dc77`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/0f1dc770503395f8035b75fb0ae4ff394d1b5dfe)
+- refactor: move CLI options validation to main() [`93d9519`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/93d95199cbcad411feef0da6e9afff06c46c7e5b)
+- build(ocpp-server): apply updates [`8768034`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/87680344f2f32b45fa89d8ae3c46d3e28634e4bf)
+- chore: move configuration specific to ocpp-server to its folder [`b00abe8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b00abe8ebfc55f1029de151e21d2b66a08293a4e)
+- build(deps-dev): apply updates [`9daf701`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/9daf701f5321be7f7ab466026b43e4714d5c3f9d)
+- build(deps-dev): apply updates [`6d037fb`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6d037fb64a050e2f7c368fabf7da9d0c56b7265e)
+- test: add ConfigurationData enums tests [`47a41cf`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/47a41cfff7de841a931f5694dbcddaf235e91bdd)
+- build(deps-dev): bump vite from 5.3.1 to 5.3.2 [`87170a8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/87170a8e77a26d5f3156f4411cff1d0e2620992d)
+- docs: document volta usage for development [`17c9163`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/17c916369498726b5dff6145a6d50261f0cb90a0)
+- build(deps-dev): apply updates [`8df621e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8df621e643de566c4eae6608ed9cfcc8271bd3aa)
+- build(ci): add ocpp-server code linter check [`ffccbf2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ffccbf2287d74bfde06179427a1eabad986d1235)
+- [autofix.ci] apply automated fixes [`1f71f83`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1f71f83fc5e112fd25fd9ce83af436fcba71ed68)
+- test: remove GetBaseReport ocpp server handler [`3bacab7`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3bacab7a1e81110d4c416570e13658d2c07ce888)
+- chore(ocpp-server): refine ruff linter configuration [`a6fac14`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a6fac14d419b2f278ce16d4505018d2ac8a179a1)
+- test(ocpp-server): add more request handlers [`5dd22b9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5dd22b9f9bed3b2cbef2ee24a5c7f4f1726a85d0)
+- build(deps): apply updates [`527e52c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/527e52c5d10f3b8bf84fae1d05d254a053dc62cc)
+- build(deps-dev): apply updates [`936ba67`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/936ba6779d56dbb7ff50810f8ee5adf0a9845a89)
+- test(ocpp-server): keep track of connected CS [`a5c2d21`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a5c2d21fa2d2bee31c329622e76cd8ac391ca37d)
+- build(deps-dev): apply updates [`b3e2979`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b3e297931b16fbd02794e62bdea7077419e07097)
+- build: fix pnpm lock file [`757468b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/757468bf17a0c5eb03ee9eb924ae5c34f8f4e5ca)
+- test(ocpp-server): add TransactionEvent handler [`22c4f1f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/22c4f1fc7fe1dc966a280de7f66e2f9fe592fbd2)
+- chore(ocpp-server): refine ruff linter configuration [`ad8df5d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ad8df5d38dd7ef010f652a4e60f4af2c67e6d908)
+- [autofix.ci] apply automated fixes [`acf133c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/acf133cd0efc14b1e45719a88dc57fac71bb1c90)
+- test: add clear cache implementation example in ocpp2 server [`a89844d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a89844d433370fc777a3a9d5ce29f7e8026b71a8)
+- test(ocpp-server): add RepeatTimer class to allow to emit OCPP messages on a regular [`aea4950`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/aea49501ed3c51f30a6d9c9a2f41ee4588cae313)
+- fix(ocpp-server): ensure the CLI options help is not truncated [`ba56e7c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ba56e7c9946964b7c28adb72cd54eeb805194f25)
+- refactor: silence linter [`52f298c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/52f298cfa6812195ad89d58d15e3e469be52db57)
+- [autofix.ci] apply automated fixes [`b738a0f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b738a0fc1c620f3836d868235af16dfb61a91a50)
+- fix(ocpp-server): silence linter [`6c0215d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6c0215d2bf5a098aceed9e9d94d75d50ff2630db)
+- build(ci): cache python poetry deps [`52238c6`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/52238c635ec00feeb9b9300a0c99443a72e15925)
+- build(deps-dev): apply updates [`6a656e3`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6a656e31e8c1c4204bb66fbc439b77232d230022)
+- build(deps-dev): apply updates [`842d677`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/842d677e39a455cee14efabb19110a3c4818a867)
+- build(ci): install pnpm deps only once [`56b900f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/56b900ff755523df397b7da2a050471a48c91d57)
+- docs: refine README.md [`783b954`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/783b954595e60cfc5e90ddd749bc8578ec2512fe)
+- build(deps-dev): apply updates [`a387f9d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a387f9d902a1dc007108f2f632427cf008ea1bf2)
+- build: recommend vscode ruff extension for ocpp-server python code [`a951fc1`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a951fc1b6194c0f65fd19ea62f119cd26283f2fd)
+- [autofix.ci] apply automated fixes [`f93800e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/f93800e55130085e7ee3509a7952c5dd2315c146)
+- refactor(ci): cleanup GH actions [`657ddf9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/657ddf9cc2c9fc8c81fd3da90803cd378987fd3a)
+- fix: fix ApplicationProtocolVersion type definition [`f2a9013`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/f2a90135692aac9beaeeb73efcb7e7aa093ec39a)
+- fix: issue 39 [`4e5c91a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4e5c91aef023b214716aeb6699edebd282cceeb9)
+- build(ocpp-server): sync poetry lock file [`cfb413a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cfb413a541334cbe3ea80fc111ccc44736442d58)
+- refactor(ci): cleanup GH actions [`fafff4e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fafff4ebf141074899e2a8590f5166542d626db5)
+- Update tests/ocpp-server/server.py [`b518537`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b5185375d64bf123c5f12e165be4175f77dd8466)
+- Update tests/ocpp-server/server.py [`ba0a759`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ba0a75922d7cba5f44b97eddc7b567f50ba920e2)
+- Update tests/ocpp-server/server.py [`0457819`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/04578196600a49d2f03710ded6ba0548cbbd87ad)
+- Update tests/ocpp-server/server.py [`39d32b7`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/39d32b7191ff76ffb277e6d116caa95e443d326c)
+- docs(ocpp-server): cleanup comment [`12925eb`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/12925ebc4afad719210d96b64571aa8bccad0fe5)
+- Merge dependabot/pip/tests/ocpp-server/ruff-0.5.0 into combined-prs-branch [`e17b6e8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e17b6e807babcf5ed7b53b03d4f3006960dfe671)
+- Merge dependabot/npm_and_yarn/vite-5.3.2 into combined-prs-branch [`85f0a4b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/85f0a4b3b5c7f47c06be1be8cbf51f3fcb88acf3)
+- [autofix.ci] apply automated fixes [`e1702c3`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e1702c3295a8b068eed8c7cf616a7996037e3651)
+- fix: corrected a error [`d4eddfa`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d4eddfaee92738e8d33f7cca0acc724b152501d4)
+- refactor(ocpp-server): refine log message [`118332f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/118332f42f7f6fb80b895794fc5f1179f78c294c)
+- Update tests/ocpp-server/server.py [`891ae31`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/891ae31da6a7a04ad29e46f3d2d2377f55c51932)
+- Update tests/ocpp-server/server.py [`7068f7f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7068f7f35112cf086e9b75ff1ee5f9fc2c1b0bb2)
+- Update tests/ocpp-server/server.py [`12798af`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/12798afb447fdc4a24998982e546a7b5334eea03)
+- fix: updated .gitignore to exclude temporary files [`57256ac`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/57256ac7ea82a86b8bf3a08a8f767e0abf4e07d4)
+- build(ci): fix task name for autofix [`e784545`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e78454533d7ea81da471ea83e615215f8c578014)
+- fix(ci): fix autofix jobs [`3e79e7e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3e79e7ebfae0acef316913324cc9ba5e9b018949)
+- build(ci): checkout repo only once [`ecf7494`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ecf7494a32490227fe31ca25df95fbe24b626f11)
+- modified conflict [`75afd2d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/75afd2d96080afd378adc6baff4d33dc33fb4ef1)
+- build(deps): add dependabot configuration for ocpp-server [`20e5e16`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/20e5e16ec38052ef88fdcf0879b0877aa3f4b1d8)
+- test: add .gitignore for ocpp-server [`0395ac6`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/0395ac6663d6257d0c9f5f9c0468691721cf6748)
+- refactor(ocpp-server): code formatting [`8430af0`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8430af0a8ef88806725ab72613f0d2385798e182)
+- test: fix OCPP 2 server startup [`339f65a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/339f65ad177a26f0c75dab3da066638ffcd7c44d)
+- test(ocpp-server): add heartbeart command handler [`115f3b1`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/115f3b177307e3a152a556db843653a499a5fe4c)
+- refactor: refine OCPP 2 mock server defaults [`dd4588b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/dd4588bbff2b31d206367724d0aa843ecc7ef7c6)
+- test(ocpp-server): add StatusNotification handler [`65c0600`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/65c0600c2ca47b47559d3d9e7bc5e56b272dc615)
+- chore: bump volta node version [`b89f8c8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b89f8c8293de89983b80f4296037b2ae2652fe91)
+- build(deps): bump sonarsource/sonarcloud-github-action [`45609cf`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/45609cfb6cc764eeb8aa0a74dc41ab2a8d1875ca)
+- docs(ocpp-server): refine README.md [`4c69efe`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4c69efeacafda3a585d526b1476572746537f8c1)
+- refactor: cleanup dependabot configuration [`8f8f989`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8f8f989efabf09ced480ebf661b54975dd7225b9)
+- chore: cleanup editorconfig config [`c2e5313`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c2e53133a6d00f5b64e66a1359f7f244730b9d02)
+- test(ocpp-server): add task to run server [`3a89ffc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3a89ffcba5b6eb9e37619f67b56aadfea2ee2a09)
+- build(ci): silence warning at building OCPP mock server [`40f37d8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/40f37d8ec1d1b46e61c2513d17a4fc30c4a45910)
+- refactor(ocpp-server): refine log message [`950b6c5`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/950b6c5a67672f45330979ff63860886dea62b67)
+- test: align OCPP2 server messages [`e1c2dac`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e1c2dac9ba7b83ab392f9c1130810a9fb07a8eaf)
+
+## [v1.3.6](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.5...v1.3.6) (2024-06-10)
+
+- build(deps-dev): apply updates [`01ffb6d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/01ffb6d3583ab4da138dad815803121f3cc6d336)
+- feat: handle CHARGE_POINT_MAX_PROFILE charging profiles at power [`3579910`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/357991053f9d8910cdfaebde426eca58f813c05f)
+- build(deps-dev): apply updates [`7b0a334`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7b0a334d2c0e42768b246cbc962718d605ca7464)
+- refactor: cleanup power limitation code [`21f68e2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/21f68e222da0b9ac5d43a70b96beaf3f13ff3926)
+- refactor: cleanup charging profiles handling code [`c76d9c8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c76d9c83dba681eeccd78dcfae661b085d7ccf2b)
+- fix: ensure no charging profile purpose TxProfile is loaded at startup [`3edfdf5`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3edfdf53dc3c75bf26b4737bb014e6e98239ef38)
+- chore: version 1.3.6 [`5d42650`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5d42650b5661ded13bc4a7f5b938f2b914410b18)
+- build(deps-dev): apply updates [`1056f1b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1056f1b4cf5e305c45351093cb062d05468c83e8)
+- build: apply volta pnpm update [`c9a92ed`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c9a92ed7a2ef6d5258f075620b7f4878ba12c454)
+- fix: fix TxProfile removal with transaction id defined at Tx stop [`d020e24`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d020e249e5206699107d6e63d3d585a7e72e7830)
+
+## [v1.3.5](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.4...v1.3.5) (2024-06-09)
+
+- test: add ErrorUtils test [`d05b53c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d05b53c7a03b8fad2e106caee68d5871cc6aac6e)
+- test: improve ErrorUtils coverage [`2d4928a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/2d4928a7237a906158f2e9652e08f0392eeabdcd)
+- build(deps-dev): apply updates [`c4387ed`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c4387ed4b3d2021034f452deeb35276fabe022e2)
+- feat: handle charging profile purpose TxProfile [`7abb61b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7abb61bb4a4e1de238c6bdb31b1a017bcf56d7b6)
+- build(deps-dev): apply updates [`65b608f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/65b608f0259f6f59138406ea327d22ecd6a19361)
+- test: add tests [`b49550e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b49550e256d11c7a30fb19cd672e5e3611d01553)
+- test: improve coverage on existing tests [`ff40d2c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ff40d2cc466140d6f18bc15b742100dd4f060d0f)
+- chore: version 1.3.5 [`cc04ce3`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cc04ce3549100de93505abdd6ff43fd3d968998d)
+- fix: restart metervalues interval if MeterValueSampleInterval is changed [`b8e3363`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b8e3363a179fcf79d8bb66f47724b377d4d38a75)
+- refactor: rename Storage.handleDBError -&gt; Storage.handleDBStorageError [`a03b18c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a03b18c4731bfd1173ff67b74a75684d3f6bb470)
+- fix: allow to set charging profile with TxProfile purpose [`65099c1`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/65099c1e7297b252523172096a8d6ded22daa702)
+- fix: avoid to modify stored charging profiles [`a629e6f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a629e6fcfeb952d77da280e69d645b17d94b2e4c)
+- test: add ConfigurationUtils test [`2a9305b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/2a9305b5ace7b5a49b5862c4282c1a88e3087da5)
+- perf: compute power limitation only once [`5b1bd2d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5b1bd2d2c3c606aeef58cb1098def9a20c50dc4e)
+- fix: refine default configuration [`a9babd5`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a9babd5006e9b70f281d4bd654923fc3d63cd733)
+- test: improve ConfigurationUtils coverage [`8598b56`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8598b5618e5acf6b9e2538faa5225ba51aa68d10)
+- test: add Utils insertAt() test [`055d4c4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/055d4c4c39c9c1fcce050019c61caf305ec1a83e)
+- test: trivial refinements [`0acbf5e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/0acbf5e6ab020dda7ddc6785347c05c144de3bd9)
+- fix: ensure ATG status is refreshed in the UI at stop [`c004d5e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c004d5e2e7d277ab99b3ee1b8ce363736529db9e)
+- test: improve ConfigurationUtils coverage [`329f14c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/329f14c004f74241c7cae1400e81740f3333eeee)
+- fix: add sanity check in ATG on connector id [`a4d9680`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a4d9680730c77debfb3b9455e4bd5b63e61ce664)
+- perf: add fastpath in array sorting helper [`8bfa4d2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8bfa4d2be3f3817654791f166f4aab1b02485005)
+- fix: fix start transaction response handling with authorize [`ae8fb16`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ae8fb16df7c98a9238e61b1b358f8fbe2aee7a74)
+- fix: fix condition at ATG configuration handling [`274f9b3`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/274f9b3662d8cf5e5fb0a361a1ae9fac86309e40)
+
+## [v1.3.4](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.3...v1.3.4) (2024-06-06)
+
+- build(deps): bump sonarsource/sonarcloud-github-action from 2.1.1 to 2.2.0 [`#1038`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1038)
+- build(deps): bump pnpm/action-setup from 3 to 4 [`#1037`](https://github.com/sap/e-mobility-charging-stations-simulator/pull/1037)
+- fix: ensure message sequence is fired at boot notification response [`#1039`](https://github.com/sap/e-mobility-charging-stations-simulator/issues/1039)
+- refactor: code formatting [`48847bc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/48847bc0d5e594c3e4c0b81c580ac6df48052668)
+- build(deps-dev): apply updates [`7445d18`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7445d18b9b9ac750b9dc3dc3638d4fd0e8aff818)
+- build(deps-dev): apply updates [`268173c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/268173c99a4ff81e1416909244ec58a6eb56dae6)
+- build(deps-dev): apply updates [`4f146c7`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4f146c7c528d96cbe53ffeb9e578dc0c7e633af2)
+- build(deps): apply updates [`1dfc15d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1dfc15d22b7ac2363a32540d43540b7a93277a67)
+- build(deps-dev): apply updates [`c8d7098`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c8d709814d13d9e4ea88a6c3a6701b37bfa858a4)
+- build(deps-dev): apply updates [`5218eec`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5218eec27c72bade7112c6b029d7d1eb1f42f871)
+- build(deps-dev): apply updates [`18f25d9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/18f25d958f1a57c34ed9d6e1de32ba9160c49dc2)
+- perf: use mnemonist CirculerBuffer [`840ca85`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/840ca85d7c40a6ee6f3a85a50e68dbea2f90acb8)
+- build(deps-dev): apply updates [`4bbeb8a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4bbeb8ad766752ba5da88513d9e77cba136ae85f)
+- build(deps-dev): apply updates [`4ed43d2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4ed43d280ab39b9beaadfa90875c71bf3b2a9507)
+- build(deps-dev): apply updates [`2f2e044`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/2f2e04446bb16844a71e8752ae29bff584267029)
+- build(deps): apply updates [`b1b7103`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/b1b7103d2089ea75bff0a15125422adf69683fc1)
+- build(deps): apply updates [`78ffd68`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/78ffd6863ea25544c4933303281c4bc7a81496f0)
+- build(deps-dev): apply updates [`a112428`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a11242837ef77110c4d4b7a6ab0fa6f9a644a253)
+- build(deps): apply updates [`f26697b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/f26697b4c9dcdf791e48e8f15160e90f6855c995)
+- build(deps-dev): apply updates [`cd01b1d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cd01b1d8f00898e27f78f4795650a78a179f0d14)
+- build(deps): apply updates [`576c4a4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/576c4a496a355c5d995325406ec2cc37416f8a8e)
+- build(deps-dev): apply updates [`8788b81`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8788b81a3dd4add12ca01b1fe989b5c7c8927d54)
+- build(deps): apply updates [`21d5c4c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/21d5c4cdd0ccc30526f290a0be994990d5feac3f)
+- fix: fix performance statistics storage [`1c818bd`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1c818bd3b021c8e660d64f9054e02d06424a3c59)
+- build(deps): apply updates [`24a0327`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/24a032720668044fc8439da3165715bab8474b26)
+- build(deps): apply updates [`47f846b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/47f846b2a74439a610321356604f2f4184cb5c0b)
+- build(deps): apply updates [`cc99095`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cc99095fab6d038ffff7ef3a2a8d33e762368670)
+- build(deps): apply updates [`73a88b8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/73a88b832be6eedd83e7b277032c8d7ba7d06a97)
+- build(deps): apply updates [`0972694`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/0972694d006542648c971026f43e5f4c8498f459)
+- build(deps-dev): apply updates [`36158bb`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/36158bb7c539757a8b4a898dd6093d6805376921)
+- build(deps): apply updates [`1f851da`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1f851da6440e4e4a31e959fcb8802b6c7f30beef)
+- chore: version 1.3.4 [`13a6494`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/13a6494f3542b9ed8773311302fa85258a1edec7)
+- refactor: cleanup OCPP utils [`01b82de`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/01b82de5b532cd149eccab7b82fe86a741289581)
+- build(deps-dev): apply updates [`55e006e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/55e006e1af126c62f26f39cd4a1f91ebd44b865d)
+- fix: ensure only circular buffer is converted to array [`312d325`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/312d3254e6581f6bc939497d610048b6c6226d6d)
+- fix: fix date handling connectorsStatus configuration file section [`1fa9df8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1fa9df8ce0e186387ecaa80efb9f40180fc36a7d)
+- fix: start heartbeat once [`d627f8e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d627f8ef31d97da80f4de5ed0cb9386472f51bbe)
+- fix: ensure boot notification response is assigned before querying [`0320e2b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/0320e2bb72ca522c3932850cce0601ab0a895ad3)
+- build(deps-dev): apply updates [`aa345d0`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/aa345d0154ff9700d8c9467c04c215ef5d07bbbf)
+- build: cleanup pnpm lock file [`fccc8f6`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fccc8f66dfabe8e91f67311f3182e26aece20293)
+- refactor: cleanup boot notification response assignation [`ab29e68`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ab29e6820d6a6d48d8ccc091ec8adae575398c3d)
+- build(deps): apply updates [`29fe6fd`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/29fe6fd834029f21b22a23a3342f86b43c277c56)
+- build(deps-dev): apply updates [`fc10596`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fc105961dd56e041fc6de1df3fd51c74723ba419)
+- refactor: refine OCPP message sending error handling [`436569b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/436569b1470c8e6187c63d974304bd1b99f35ba3)
+- fix: fix clear charging profiles with no connector in payload [`ec6dd88`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/ec6dd88b156eac9a14afd0d349ed58bb02fa920e)
+- fix: start web socket ping interval at successful connection [`99100f9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/99100f9c8709a7ead56c1e2745db80a75e8f6f79)
+- build(deps-dev): apply updates [`3ebab70`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3ebab7003594d1c243bcca4ef90007dc63680a52)
+- build(deps-dev): apply updates [`e4006e4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e4006e4283d9150be5bd2f1050ff78c979a203c1)
+- fix: ensure charging profiles not related to the current transaction are [`626d3ce`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/626d3ce5e6dfdc848cb2bb5833044fe6fbe68324)
+- fix: ensure inflight requests id cannot be duplicated [`3024d5b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/3024d5b2497e97bdd355243a5d236fa3f7a4d874)
+- build(deps-dev): apply updates [`69d995b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/69d995b2c8683703e7d9ce42fdd73ec02209482f)
+- fix: avoid endless loop at remote start transaction [`d0b7173`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d0b71736d6fced71a50c67e1f1e7ca842068e43e)
+- build(deps-dev): apply updates [`a6f03d2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a6f03d2ddc8e28752b94cb0a43687d70c16683a6)
+- build(deps-dev): apply updates [`fa394dc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fa394dc9dd5f30d5121c2ce6ed11ba5d7c30bcd3)
+- fix: reset authorize fields connector status after reservation [`239194e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/239194e9867eaea7d34fc3c8d3bff3360113d8dc)
+- build(deps-dev): apply updates [`df3e0f8`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/df3e0f8da05b9680213ccd3e3beee6686f296cd1)
+- refactor: cleanup Infinity usage [`cffc32b`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/cffc32b7bc5a6569525e92b562af8a93760bc339)
+- build(deps-dev): apply updates [`fa30db1`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/fa30db101a2d11774bd9b29ca806597865ea06bf)
+- build: fix pnpm lockfile [`5e1bf42`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5e1bf4295e75408830ff46bb704a5139572a4120)
+- fix: properly handle undefined connector id at remote start transaction [`c0bbb3e`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c0bbb3eaf0c5dc704ea92820a2666a68ffdc27ff)
+- fix: fix error handling default options definition [`30695dc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/30695dcf67470f149e349d196b3d016482d11a17)
+- build(deps-dev): apply updates [`9e66896`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/9e668960f040bf5da86a6d6e7c477d4ad39dfd04)
+- build: bump volta node version [`494983d`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/494983d465621a426f5dc92fd7cccc62821616f9)
+- build(deps-dev): apply updates [`bb57ea0`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/bb57ea07805938bec42dbee3da6bfda569a1b9bb)
+- refactor: convert npx to pnpm exec [`e1486a4`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e1486a4c8ce0142f2efc57c87fc8e8e05745a13f)
+- refactor: cleanup boot notification response handling [`52f1dd5`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/52f1dd569da1eac66de821796cdfbcef09b48c87)
+- refactor: cleanup connection retries logic [`aa5f2ea`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/aa5f2ea2958ef34e8d34e20edbe8486b8911d82b)
+- fix: pickup not reserved connector at remote start transaction [`8946ba9`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8946ba9db13e225292a62b8f4bdcd1419792697a)
+- refactor: cleanup eslint usage [`c69ae13`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/c69ae13bb71df8f2017e21d919979b73dc6184f9)
+- fix: fix firmware update in progress detection [`382b62c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/382b62c45d665e66d4a2f3ad5ac7ecc6396859c7)
+- fix: fix log files path resolution at GetDiagnostics [`e56bbec`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e56bbec564af27559aade33d375dc890e882858e)
+- docs: refine code comments [`1d85f41`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/1d85f415a46d0d3355ee013e449ad99d3aee9418)
+- build(deps): bump sonarsource/sonarcloud-github-action [`e48376c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/e48376cebb2f0e5d6c670b432e1b524e21702d3a)
+- build(ci): rely on packageManager field to setup pnpm [`7bba320`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7bba3209fa975827c841aeab8fa20b4837ca64b1)
+- refactor: cleanup boot notification handling internal handling [`d9c13bc`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/d9c13bcac891b8686dbba36ae5c10f87948e3614)
+- fix: fix remote transaction start debug log [`7b08e7f`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/7b08e7f332d1146fa5990cf29e08e328343819f1)
+- fix: ensure registration status is deleted if invalid boot notification [`18c6df2`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/18c6df2b1102c307cbe8eb0c04d85c82045abf15)
+- refactor: cleanup string literal variables handling [`4c6f356`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4c6f35659fb67d395adc035ef80c566eb6eef79e)
+- perf: resize circular array to sensible default [`9b5cf75`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/9b5cf75581e0338885b8e2ea408ea5a3f391d0ec)
+- fix(ci): silence linter [`4293e51`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/4293e5179eabbc8f42a1c06064d15c0ec7d1ca3f)
+- refactor: helper cleanup [`f69ca7c`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/f69ca7ca5f551a427e3181d38bc893cb68b2b542)
+
+## [v1.3.3](https://github.com/sap/e-mobility-charging-stations-simulator/compare/v1.3.2...v1.3.3) (2024-04-30)
 
 - fix: avoid duplicated slash in connection url [`#1034`](https://github.com/sap/e-mobility-charging-stations-simulator/issues/1034)
 - chore: switch to pnpm 9.x.x [`6eddb71`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6eddb71902ac0d0552f705190aaf62525f07c476)
 - build(deps-dev): apply updates [`a637f99`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/a637f99f1d9a63bed1338809a5dbfab26925babe)
 - build(deps): apply updates [`de073a7`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/de073a748bcd60308615b81c3f6da817639461a1)
 - build(deps-dev): apply updates [`5b7bbdb`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/5b7bbdba3612e2f5748a4c21b46ba7bee89ddaba)
+- chore: version 1.3.3 [`6deeebd`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/6deeebd11133a5c4b47452d9d9ab8cf5759e7c8a)
 - build(deps-dev): apply updates [`98d6958`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/98d6958a6225cecc92e2f8b676c91be9fffd857b)
 - refactor: silence typing errors [`314793a`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/314793aaf25bf1a99deb3f8209c09421235942ba)
 - build: bump volta pnpm version [`8fb8f42`](https://github.com/sap/e-mobility-charging-stations-simulator/commit/8fb8f42ace8b9295865adebaeb7845e064c05e71)
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644 (file)
index 0000000..bcbe607
--- /dev/null
@@ -0,0 +1 @@
+* @jerome-benoit @olivierbagot
index cd108fd5e3b3a5240611a2756a27d1806ba3aaea..e86ea3da52ee4bcf14792bbdda7c89c6c99c2082 100644 (file)
@@ -31,6 +31,12 @@ The following rule governs code contributions:
 - Contributions must be licensed under the [Apache 2.0 License](./LICENSE)
 - Due to legal reasons, contributors will be asked to accept a Developer Certificate of Origin (DCO) when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/).
 
+## Contributing with AI-generated code
+
+As artificial intelligence evolves, AI-generated code is becoming valuable for many software projects, including open-source initiatives. While we recognize the potential benefits of incorporating AI-generated content into our open-source projects there a certain requirements that need to be reflected and adhered to when making contributions.
+
+Please see our [guideline for AI-generated code contributions to SAP Open Source Software Projects](https://github.com/SAP/.github/blob/main/CONTRIBUTING_USING_GENAI.md) for these requirements.
+
 ## Issues and Planning
 
 - We use GitHub issues to track bugs and enhancement requests.
index 063c839d6d6ac599ec9bd83fa1930a41e99924dc..c03b3f5e388197f395337b62047254a4ecc31f2d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 [![GitHub commit activity (main)](https://img.shields.io/github/commit-activity/m/SAP/e-mobility-charging-stations-simulator/main?color=brightgreen&logo=github)](https://github.com/SAP/e-mobility-charging-stations-simulator/graphs/commit-activity)
 [![CI workflow](https://github.com/SAP/e-mobility-charging-stations-simulator/actions/workflows/ci.yml/badge.svg)](https://github.com/SAP/e-mobility-charging-stations-simulator/actions/workflows/ci.yml)
 [![REUSE status](https://api.reuse.software/badge/github.com/SAP/e-mobility-charging-stations-simulator)](https://api.reuse.software/info/github.com/SAP/e-mobility-charging-stations-simulator)
-[![Javascript Standard Style Guide](<https://badgen.net/static/code style/standard/green>)](https://standardjs.com)
+[![neostandard Javascript Code Style](<https://badgen.net/static/code style/neostandard/green>)](https://github.com/neostandard/neostandard)
 
 </div>
 
@@ -24,13 +24,17 @@ Simple [node.js](https://nodejs.org/) software to simulate and scale a set of ch
     - [Windows](#windows)
     - [MacOSX](#macosx)
     - [GNU/Linux](#gnulinux)
+  - [Development prerequisites (optional)](#development-prerequisites-optional)
+    - [Unix](#unix)
+    - [Windows](#windows-1)
+  - [Branching model](#branching-model)
   - [Dependencies](#dependencies)
 - [Initial configuration](#initial-configuration)
 - [Start simulator](#start-simulator)
 - [Start Web UI](#start-web-ui)
 - [Configuration files syntax](#configuration-files-syntax)
   - [Charging stations simulator configuration](#charging-stations-simulator-configuration)
-  - [Charging station configuration template](#charging-station-configuration-template)
+  - [Charging station template configuration](#charging-station-template-configuration)
   - [Charging station configuration](#charging-station-configuration)
 - [Docker](#docker)
 - [OCPP-J commands supported](#ocpp-j-commands-supported)
@@ -70,11 +74,35 @@ brew install node
 
 #### GNU/Linux
 
-- [NodeSource](https://github.com/nodesource/distributions) Node.js Binary Distributions for all supported versions.
+- [NodeSource](https://github.com/nodesource/distributions) node.js binary distributions for all supported versions.
 
-#### Dependencies
+### Development prerequisites (optional)
 
-Enable corepack if not already done and install latest pnpm version:
+Install [volta](https://volta.sh/) for managing automatically the node.js runtime and package manager version:
+
+#### Unix
+
+```shell
+curl https://get.volta.sh | bash
+```
+
+#### Windows
+
+```powershell
+choco install -y volta
+```
+
+Setup [volta](https://volta.sh/) with [pnpm](https://github.com/pnpm/pnpm) package manager support: https://docs.volta.sh/advanced/pnpm
+
+### Branching model
+
+The `main` branch is the default development branch.  
+The `vX` branches are the maintenance branches for the corresponding major version `X`.  
+The `vX.Y` branches are the maintenance branches for the corresponding major and minor version `X.Y`.
+
+### Dependencies
+
+Enable corepack, if [volta](https://volta.sh/) is not installed and configured, and install latest pnpm version:
 
 ```shell
 corepack enable
@@ -160,7 +188,7 @@ But the modifications to test have to be done to the files in the build target d
 - **dynamicPool** (experimental):
   Dynamically sized worker pool executing a fixed total number of simulated charging stations
 
-### Charging station configuration template
+### Charging station template configuration
 
 **src/assets/station-templates/\<name\>.json**:
 
@@ -821,7 +849,6 @@ Set the Websocket header _Sec-Websocket-Protocol_ to `ui0.0.1`.
 Examples:
 
 - **Start Transaction**
-
   - Request:  
     `ProcedureName`: 'startTransaction'  
     `PDU`: {  
@@ -839,7 +866,6 @@ Examples:
     }
 
 - **Stop Transaction**
-
   - Request:  
     `ProcedureName`: 'stopTransaction'  
     `PDU`: {  
@@ -856,7 +882,6 @@ Examples:
     }
 
 - **Status Notification**
-
   - Request:  
     `ProcedureName`: 'statusNotification'  
     `PDU`: {  
@@ -875,7 +900,6 @@ Examples:
     }
 
 - **Heartbeat**
-
   - Request:  
     `ProcedureName`: 'heartbeat'  
     `PDU`: {  
diff --git a/REUSE.toml b/REUSE.toml
new file mode 100644 (file)
index 0000000..9c2b5f9
--- /dev/null
@@ -0,0 +1,11 @@
+version = 1
+SPDX-PackageName = "e-mobility-charging-stations-simulator"
+SPDX-PackageSupplier = "Jerome Benoit <jerome.benoit@sap.com>"
+SPDX-PackageDownloadLocation = "<https://github.com/sap/e-mobility-charging-stations-simulator>"
+SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
+
+[[annotations]]
+path = "**"
+precedence = "aggregate"
+SPDX-FileCopyrightText = "2021-2024 SAP SE or an SAP affiliate company and e-mobility-charging-stations-simulator contributors"
+SPDX-License-Identifier = "Apache-2.0"
index baef3220648d1643421afee14539079a8b2aa7e2..93fbcd3ee79e545a5f10ce07e0a8ee4ea178c0d8 100644 (file)
@@ -1,10 +1,11 @@
 FROM node:lts-alpine AS builder
 
-# Build simulator and dashboard
+# Build simulator
 WORKDIR /usr/builder
 COPY . ./
 RUN set -ex \
   && apk add --no-cache --virtual .gyp build-base python3 \
+  && npm install -g --ignore-scripts corepack \
   && corepack enable \
   && corepack prepare pnpm@latest --activate \
   && pnpm set progress=false \
@@ -13,10 +14,7 @@ RUN set -ex \
   && cp docker/config.json src/assets/config.json \
   && cp docker/idtags.json src/assets/idtags.json \
   && pnpm build \
-  && apk del .gyp \
-  && cd ui/web \
-  && cp src/assets/config-template.json public/config.json \
-  && pnpm build
+  && apk del .gyp
 
 FROM node:lts-alpine
 
@@ -26,14 +24,13 @@ ARG MAX_OLD_SPACE_SIZE=768
 ENV NODE_OPTIONS="--stack-trace-limit=${STACK_TRACE_LIMIT} --max-old-space-size=${MAX_OLD_SPACE_SIZE}"
 
 WORKDIR /usr/app
-COPY --from=builder /usr/builder/ui/web ./ui/web
 COPY --from=builder /usr/builder/node_modules ./node_modules
 COPY --from=builder /usr/builder/dist ./dist
 COPY package.json README.md LICENSE ./
-COPY docker/start.sh /start.sh
+COPY docker/run.sh /run.sh
 COPY docker/autoconfig.sh /autoconfig.sh
 RUN set -ex \
-  && chmod +x /start.sh \
+  && chmod +x /run.sh \
   && chmod +x /autoconfig.sh
 
-CMD /autoconfig.sh && /start.sh
+ENTRYPOINT ["/bin/sh", "-c", "/autoconfig.sh && /run.sh"]
index 24973efcee5b31cfec90f402cd5e9dc3a2089756..b8067bffa4b33d14727d9d068cb7ca4cc3975dfa 100755 (executable)
@@ -9,7 +9,6 @@ then
 
   cp $emobility_install_dir/dist/assets/configs-docker/$emobility_server_type-$emobility_service_type-$emobility_landscape.json $emobility_install_dir/dist/assets/config.json
   cp $emobility_install_dir/dist/assets/configs-docker/$emobility_server_type-$emobility_service_type-$emobility_landscape-idtags.json $emobility_install_dir/dist/assets/idtags.json
-  cp $emobility_install_dir/dist/assets/configs-docker/$emobility_server_type-$emobility_service_type-$emobility_landscape-webui.json $emobility_install_dir/ui/web/public/config.json
 else
   echo "no emobility env defined, start with default configuration"
 fi
index c207106a03271e955b4566ee3fa8279f8e4cf612..5bd7ccee417749c3a558520b39cdb30419db2efa 100644 (file)
@@ -15,6 +15,9 @@
   },
   "uiServer": {
     "enabled": true,
+    "options": {
+      "host": "::"
+    },
     "type": "ws",
     "authentication": {
       "enabled": true,
index 999f8a7e14a98a7622056cb9697156709a2a75a8..e5044cf1bb62bafd6d8dbdccef8332fa49df67a9 100644 (file)
@@ -1,4 +1,3 @@
-version: '3.7'
 networks:
   ev_network:
     driver: bridge
@@ -12,7 +11,5 @@ services:
         MAX_OLD_SPACE_SIZE: 768
     networks:
       - ev_network
-    expose:
-      - 8080
     ports:
-      - '3030:3030'
+      - 8080:8080
diff --git a/docker/run.sh b/docker/run.sh
new file mode 100755 (executable)
index 0000000..189983a
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+node dist/start.js
diff --git a/docker/start.sh b/docker/start.sh
deleted file mode 100755 (executable)
index 65a466c..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-
-node dist/start.js &
-node ui/web/start.js
index a08e71ee4ee98985661b8042db98cf1b1d955856..40aac8a202f7bacd39bee687b9ded0e5c0049560 100644 (file)
@@ -1,22 +1,25 @@
 {
   "folders": [
     {
-      "path": "."
+      "path": ".",
     },
     {
-      "path": "ui/web"
-    }
+      "path": "ui/web",
+    },
+    {
+      "path": "tests/ocpp-server",
+    },
   ],
   "settings": {
     "nodejs-testing.extensions": [
       {
         "extensions": ["mjs", "cjs", "js"],
-        "parameters": []
+        "parameters": [],
       },
       {
         "extensions": ["mts", "cts", "ts"],
-        "parameters": ["--import", "tsx"]
-      }
-    ]
-  }
+        "parameters": ["--import", "tsx"],
+      },
+    ],
+  },
 }
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644 (file)
index 0000000..52728b3
--- /dev/null
@@ -0,0 +1,148 @@
+/* eslint-disable n/no-unpublished-import */
+import cspellConfigs from '@cspell/eslint-plugin/configs'
+import js from '@eslint/js'
+import jsdoc from 'eslint-plugin-jsdoc'
+import perfectionist from 'eslint-plugin-perfectionist'
+import pluginVue from 'eslint-plugin-vue'
+import { defineConfig } from 'eslint/config'
+import neostandard, { plugins } from 'neostandard'
+
+export default defineConfig([
+  {
+    ignores: ['**/dist/**'],
+  },
+  cspellConfigs.recommended,
+  {
+    rules: {
+      '@cspell/spellchecker': [
+        'warn',
+        {
+          autoFix: true,
+          cspell: {
+            words: [
+              'DECI',
+              'CENTI',
+              'MILLI',
+              'Benoit',
+              'chargingstations',
+              'ctrlr',
+              'csms',
+              'idtag',
+              'idtags',
+              'iccid',
+              'imsi',
+              'ocpp',
+              'onconnection',
+              'evse',
+              'evses',
+              'kvar',
+              'kvarh',
+              'varh',
+              'rfid',
+              'workerset',
+              'logform',
+              'mnemonist',
+              'poolifier',
+              'measurand',
+              'measurands',
+              'mikro',
+              'neostandard',
+              'recurrency',
+              'shutdowning',
+              'VCAP',
+              'workerd',
+            ],
+          },
+        },
+      ],
+    },
+  },
+  js.configs.recommended,
+  plugins.promise.configs['flat/recommended'],
+  ...plugins.n.configs['flat/mixed-esm-and-cjs'],
+  jsdoc.configs['flat/recommended-typescript'],
+  {
+    rules: {
+      'jsdoc/check-tag-names': [
+        'warn',
+        {
+          definedTags: ['defaultValue', 'experimental', 'typeParam'],
+          typed: true,
+        },
+      ],
+    },
+  },
+  ...pluginVue.configs['flat/recommended'],
+  {
+    files: ['**/*.vue'],
+    languageOptions: {
+      globals: {
+        localStorage: 'readonly',
+      },
+      parserOptions: {
+        parser: '@typescript-eslint/parser',
+      },
+    },
+  },
+  ...plugins['typescript-eslint'].config(
+    {
+      extends: [
+        ...plugins['typescript-eslint'].configs.strictTypeChecked,
+        ...plugins['typescript-eslint'].configs.stylisticTypeChecked,
+      ],
+      files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts', '*/**.vue'],
+      languageOptions: {
+        parserOptions: {
+          projectService: true,
+          // eslint-disable-next-line n/no-unsupported-features/node-builtins
+          tsconfigRootDir: import.meta.dirname,
+        },
+      },
+    },
+    {
+      files: ['**/*.js', '**/*.mjs', '**/*.cjs'],
+      ...plugins['typescript-eslint'].configs.disableTypeChecked,
+    }
+  ),
+  perfectionist.configs['recommended-natural'],
+  {
+    files: ['**/*.vue'],
+    rules: {
+      'perfectionist/sort-vue-attributes': 'off',
+    },
+  },
+  ...neostandard({
+    ts: true,
+  }),
+  {
+    files: [
+      'src/charging-station/Bootstrap.ts',
+      'src/charging-station/ChargingStation.ts',
+      'src/charging-station/Helpers.ts',
+      'src/charging-station/ocpp/OCPPServiceUtils.ts',
+      'src/charging-station/ocpp/1.6/OCPP16ResponseService.ts',
+      'src/performance/PerformanceStatistics.ts',
+    ],
+    rules: {
+      '@stylistic/operator-linebreak': 'off',
+    },
+  },
+  {
+    files: ['src/scripts/*.cjs'],
+    rules: {
+      '@typescript-eslint/no-require-imports': 'off',
+    },
+  },
+  {
+    files: ['tests/utils/Utils.test.ts'],
+    rules: {
+      '@typescript-eslint/no-unsafe-member-access': 'off',
+    },
+  },
+  {
+    files: ['ui/web/src/components/Container.vue', 'ui/web/src/components/buttons/Button.vue'],
+    rules: {
+      'vue/multi-word-component-names': 'off',
+    },
+  },
+])
index 1291cbd2cc3dad2f96864021cd194af178944c58..d2796255b90658960a17afbf5952ad2940154538 100644 (file)
@@ -4,7 +4,7 @@ import { Constants } from './src/utils/index.js'
 
 export default defineConfig({
   dbName: `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`,
+  debug: true,
   entities: ['./dist/types/orm/entities/*.js'],
   entitiesTs: ['./src/types/orm/entities/*.ts'],
-  debug: true
 })
index 102fd444060138255c3a6c68fbf018ee9b7de1f5..60e780732cc25ac9824bb573dbe15b2bdf14cd84 100644 (file)
@@ -1,16 +1,16 @@
 {
   "$schema": "https://json.schemastore.org/package",
   "name": "e-mobility-charging-stations-simulator",
-  "version": "1.3.3",
+  "version": "2.0.10",
   "engines": {
-    "node": ">=18.18.0",
+    "node": ">=20.11.0",
     "pnpm": ">=9.0.0"
   },
   "volta": {
-    "node": "22.2.0",
-    "pnpm": "9.1.2"
+    "node": "22.17.0",
+    "pnpm": "10.12.4"
   },
-  "packageManager": "pnpm@9.1.2",
+  "packageManager": "pnpm@10.12.4",
   "repository": {
     "type": "git",
     "url": "https://github.com/sap/e-mobility-charging-stations-simulator.git"
   ],
   "type": "module",
   "exports": "./dist/start.js",
-  "auto-changelog": {
-    "commitUrl": "https://github.com/sap/e-mobility-charging-stations-simulator/commit/{id}",
-    "issueUrl": "https://github.com/sap/e-mobility-charging-stations-simulator/issues/{id}",
-    "mergeUrl": "https://github.com/sap/e-mobility-charging-stations-simulator/pull/{id}",
-    "compareUrl": "https://github.com/sap/e-mobility-charging-stations-simulator/compare/{from}...{to}"
-  },
   "mikro-orm": {
     "tsConfigPath": "./tsconfig-mikro-orm.json",
     "useTsNode": true
   },
   "scripts": {
-    "prepare": "node prepare.js",
-    "build-requirements": "node --no-warnings build-requirements.js",
+    "prepare": "node scripts/prepare.js",
+    "build-requirements": "node scripts/build-requirements.js",
     "start": "pnpm build && cross-env NODE_ENV=production node dist/start.js",
     "start:dev": "pnpm build:dev && cross-env NODE_ENV=development node --enable-source-maps dist/start.js",
     "start:dev:debug": "pnpm build:dev && cross-env NODE_ENV=development node --enable-source-maps --inspect dist/start.js",
@@ -62,7 +56,7 @@
     "start:flameprof": "cross-env NODE_ENV=production clinic flame -- node --enable-source-maps dist/start.js",
     "start:bubbleprof": "cross-env NODE_ENV=production clinic bubbleprof -- node --enable-source-maps dist/start.js",
     "start:heapprofiler": "cross-env NODE_ENV=production clinic heapprofiler -- node --enable-source-maps dist/start.js",
-    "esbuild": "pnpm build-requirements && node bundle.js",
+    "esbuild": "pnpm build-requirements && node scripts/bundle.js",
     "build": "pnpm esbuild",
     "build:dev": "cross-env BUILD=development pnpm esbuild",
     "build:cf": "pnpm clean:node_modules && pnpm exec cross-env SKIP_PREINSTALL=1 npm install && pnpm build",
     "build:entities": "tsc -p tsconfig-mikro-orm.json",
     "clean:dist": "pnpm exec rimraf dist",
     "clean:node_modules": "pnpm exec rimraf node_modules",
-    "lint": "cross-env TIMING=1 eslint --cache src tests .eslintrc.cjs bundle.js mikro-orm.config-template.ts",
-    "lint:fix": "cross-env TIMING=1 eslint --cache --fix src tests .eslintrc.cjs bundle.js mikro-orm.config-template.ts",
-    "format": "prettier --cache --write .; eslint --cache --fix src .eslintrc.cjs tests bundle.js mikro-orm.config-template.ts",
+    "lint": "cross-env TIMING=1 eslint --cache src tests scripts ./*.js ./*.ts",
+    "lint:fix": "cross-env TIMING=1 eslint --cache --fix src tests scripts ./*.js ./*.ts",
+    "format": "prettier --cache --write .; eslint --cache --fix src tests scripts ./*.js ./*.ts",
     "test": "glob -c \"c8 node --import tsx --test\" \"tests/**/*.test.ts\"",
     "test:debug": "glob -c \"node --import tsx --test --inspect\" \"tests/**/*.test.ts\"",
     "coverage": "c8 report --reporter=lcov",
     "coverage:html": "c8 report --reporter=html",
     "clinic:clean": "clinic clean",
-    "release": "release-it"
+    "sea": "pnpm exec rimraf ./dist/evse-simulator ./dist/evse-simulator.blob && node --experimental-sea-config sea-config.json && pnpm dlx ncp $(volta which node || n which lts || nvm which node || command -v node) ./dist/evse-simulator && pnpm dlx postject ./dist/evse-simulator NODE_SEA_BLOB ./dist/evse-simulator.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 && pnpm exec rimraf ./dist/evse-simulator.blob"
   },
   "pnpm": {
     "overrides": {
       "d3-color": "^3.1.0",
       "ejs": "^3.1.9",
       "got": "^12.6.1",
+      "nanoid": "^3.3.8",
       "semver": "^7.5.3",
-      "uuid": "^9.0.0",
-      "tough-cookie": "^4.1.3"
+      "tough-cookie": "^4.1.3",
+      "uuid": "^9.0.0"
     }
   },
   "dependencies": {
-    "@mikro-orm/core": "^6.2.7",
-    "@mikro-orm/mariadb": "^6.2.7",
-    "@mikro-orm/reflection": "^6.2.7",
-    "@mikro-orm/sqlite": "^6.2.7",
-    "ajv": "^8.13.0",
+    "@mikro-orm/core": "^6.4.16",
+    "@mikro-orm/mariadb": "^6.4.16",
+    "@mikro-orm/reflection": "^6.4.16",
+    "@mikro-orm/sqlite": "^6.4.16",
+    "ajv": "^8.17.1",
     "ajv-formats": "^3.0.1",
     "basic-ftp": "^5.0.5",
-    "chalk": "^5.3.0",
-    "date-fns": "^3.6.0",
+    "chalk": "^5.4.1",
+    "date-fns": "^4.1.0",
     "http-status-codes": "^2.3.0",
-    "logform": "^2.6.0",
-    "mnemonist": "0.40.0-rc1",
-    "mongodb": "^6.6.2",
-    "poolifier": "^4.0.10",
-    "rambda": "^9.2.0",
-    "tar": "^7.1.0",
-    "winston": "^3.13.0",
+    "logform": "^2.7.0",
+    "mnemonist": "0.40.3",
+    "mongodb": "^6.17.0",
+    "poolifier": "^5.0.2",
+    "tar": "^7.4.3",
+    "winston": "^3.17.0",
     "winston-daily-rotate-file": "^5.0.0",
-    "ws": "^8.17.0"
+    "ws": "^8.18.3"
   },
   "optionalDependencies": {
-    "bufferutil": "^4.0.8",
-    "utf-8-validate": "^6.0.4"
+    "bufferutil": "^4.0.9",
+    "utf-8-validate": "^6.0.5"
   },
   "devDependencies": {
-    "@commitlint/cli": "^19.3.0",
-    "@commitlint/config-conventional": "^19.2.2",
-    "@mikro-orm/cli": "^6.2.7",
-    "@release-it/bumper": "^6.0.1",
-    "@types/node": "^20.12.12",
-    "@types/semver": "^7.5.8",
-    "@types/ws": "^8.5.10",
-    "@typescript-eslint/eslint-plugin": "^7.10.0",
-    "@typescript-eslint/parser": "^7.10.0",
-    "auto-changelog": "^2.4.0",
-    "c8": "^9.1.0",
+    "@commitlint/cli": "^19.8.1",
+    "@commitlint/config-conventional": "^19.8.1",
+    "@cspell/eslint-plugin": "^9.1.2",
+    "@eslint/js": "^9.30.1",
+    "@mikro-orm/cli": "^6.4.16",
+    "@std/expect": "npm:@jsr/std__expect@^1.0.16",
+    "@types/node": "^24.0.10",
+    "@types/semver": "^7.7.0",
+    "@types/ws": "^8.18.1",
+    "c8": "^10.1.3",
     "clinic": "^13.0.0",
     "cross-env": "^7.0.3",
-    "esbuild": "^0.21.3",
+    "esbuild": "^0.25.5",
     "esbuild-plugin-clean": "^1.0.1",
     "esbuild-plugin-copy": "^2.1.1",
-    "eslint": "^8.57.0",
-    "eslint-config-love": "^47.0.0",
-    "eslint-config-standard": "^17.1.0",
-    "eslint-define-config": "^2.1.0",
-    "eslint-import-resolver-typescript": "^3.6.1",
-    "eslint-plugin-import": "^2.29.1",
-    "eslint-plugin-jsdoc": "^48.2.5",
-    "eslint-plugin-n": "^17.7.0",
-    "eslint-plugin-simple-import-sort": "^12.1.0",
-    "eslint-plugin-tsdoc": "^0.2.17",
-    "expect": "^29.7.0",
-    "glob": "^10.3.15",
-    "husky": "^9.0.11",
-    "lint-staged": "^15.2.2",
-    "prettier": "^3.2.5",
-    "release-it": "^17.3.0",
-    "rimraf": "^5.0.7",
-    "semver": "^7.6.2",
+    "eslint": "^9.30.1",
+    "eslint-plugin-jsdoc": "^51.3.3",
+    "eslint-plugin-perfectionist": "^4.15.0",
+    "eslint-plugin-vue": "^10.3.0",
+    "glob": "^11.0.3",
+    "husky": "^9.1.7",
+    "lint-staged": "^16.1.2",
+    "neostandard": "^0.12.2",
+    "prettier": "^3.6.2",
+    "rimraf": "^6.0.1",
+    "semver": "^7.7.2",
     "ts-node": "^10.9.2",
-    "tsx": "^4.10.5",
-    "typescript": "~5.4.5"
+    "tsx": "^4.20.3",
+    "typescript": "~5.8.3",
+    "vue-eslint-parser": "^10.2.0"
   }
 }
index 8d2df1b7e00e0819db1bad8c92eea7fd9a2e96e1..832857520583f229d2565613049e1e6c82122f99 100644 (file)
@@ -9,6 +9,7 @@ overrides:
   d3-color: ^3.1.0
   ejs: ^3.1.9
   got: ^12.6.1
+  nanoid: ^3.3.8
   semver: ^7.5.3
   uuid: ^9.0.0
   tough-cookie: ^4.1.3
@@ -18,103 +19,90 @@ importers:
   .:
     dependencies:
       '@mikro-orm/core':
-        specifier: ^6.2.7
-        version: 6.2.7
+        specifier: ^6.4.16
+        version: 6.4.16
       '@mikro-orm/mariadb':
-        specifier: ^6.2.7
-        version: 6.2.7(@mikro-orm/core@6.2.7)
+        specifier: ^6.4.16
+        version: 6.4.16(@mikro-orm/core@6.4.16)
       '@mikro-orm/reflection':
-        specifier: ^6.2.7
-        version: 6.2.7(@mikro-orm/core@6.2.7)
+        specifier: ^6.4.16
+        version: 6.4.16(@mikro-orm/core@6.4.16)
       '@mikro-orm/sqlite':
-        specifier: ^6.2.7
-        version: 6.2.7(@mikro-orm/core@6.2.7)
+        specifier: ^6.4.16
+        version: 6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)
       ajv:
-        specifier: ^8.13.0
-        version: 8.13.0
+        specifier: ^8.17.1
+        version: 8.17.1
       ajv-formats:
         specifier: ^3.0.1
-        version: 3.0.1(ajv@8.13.0)
+        version: 3.0.1(ajv@8.17.1)
       basic-ftp:
         specifier: ^5.0.5
         version: 5.0.5
       chalk:
-        specifier: ^5.3.0
-        version: 5.3.0
+        specifier: ^5.4.1
+        version: 5.4.1
       date-fns:
-        specifier: ^3.6.0
-        version: 3.6.0
+        specifier: ^4.1.0
+        version: 4.1.0
       http-status-codes:
         specifier: ^2.3.0
         version: 2.3.0
       logform:
-        specifier: ^2.6.0
-        version: 2.6.0
+        specifier: ^2.7.0
+        version: 2.7.0
       mnemonist:
-        specifier: 0.40.0-rc1
-        version: 0.40.0-rc1
+        specifier: 0.40.3
+        version: 0.40.3
       mongodb:
-        specifier: ^6.6.2
-        version: 6.6.2(socks@2.8.3)
+        specifier: ^6.17.0
+        version: 6.17.0(socks@2.8.5)
       poolifier:
-        specifier: ^4.0.10
-        version: 4.0.10
-      rambda:
-        specifier: ^9.2.0
-        version: 9.2.0
+        specifier: ^5.0.2
+        version: 5.0.2
       tar:
-        specifier: ^7.1.0
-        version: 7.1.0
+        specifier: ^7.4.3
+        version: 7.4.3
       winston:
-        specifier: ^3.13.0
-        version: 3.13.0
+        specifier: ^3.17.0
+        version: 3.17.0
       winston-daily-rotate-file:
         specifier: ^5.0.0
-        version: 5.0.0(winston@3.13.0)
+        version: 5.0.0(winston@3.17.0)
       ws:
-        specifier: ^8.17.0
-        version: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-    optionalDependencies:
-      bufferutil:
-        specifier: ^4.0.8
-        version: 4.0.8
-      utf-8-validate:
-        specifier: ^6.0.4
-        version: 6.0.4
+        specifier: ^8.18.3
+        version: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
     devDependencies:
       '@commitlint/cli':
-        specifier: ^19.3.0
-        version: 19.3.0(@types/node@20.12.12)(typescript@5.4.5)
+        specifier: ^19.8.1
+        version: 19.8.1(@types/node@24.0.10)(typescript@5.8.3)
       '@commitlint/config-conventional':
-        specifier: ^19.2.2
-        version: 19.2.2
+        specifier: ^19.8.1
+        version: 19.8.1
+      '@cspell/eslint-plugin':
+        specifier: ^9.1.2
+        version: 9.1.2(eslint@9.30.1(jiti@2.4.2))
+      '@eslint/js':
+        specifier: ^9.30.1
+        version: 9.30.1
       '@mikro-orm/cli':
-        specifier: ^6.2.7
-        version: 6.2.7
-      '@release-it/bumper':
-        specifier: ^6.0.1
-        version: 6.0.1(release-it@17.3.0(typescript@5.4.5))
+        specifier: ^6.4.16
+        version: 6.4.16(mariadb@3.4.2)
+      '@std/expect':
+        specifier: npm:@jsr/std__expect@^1.0.16
+        version: '@jsr/std__expect@1.0.16'
       '@types/node':
-        specifier: ^20.12.12
-        version: 20.12.12
+        specifier: ^24.0.10
+        version: 24.0.10
       '@types/semver':
-        specifier: ^7.5.8
-        version: 7.5.8
+        specifier: ^7.7.0
+        version: 7.7.0
       '@types/ws':
-        specifier: ^8.5.10
-        version: 8.5.10
-      '@typescript-eslint/eslint-plugin':
-        specifier: ^7.10.0
-        version: 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/parser':
-        specifier: ^7.10.0
-        version: 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      auto-changelog:
-        specifier: ^2.4.0
-        version: 2.4.0(encoding@0.1.13)
+        specifier: ^8.18.1
+        version: 8.18.1
       c8:
-        specifier: ^9.1.0
-        version: 9.1.0
+        specifier: ^10.1.3
+        version: 10.1.3
       clinic:
         specifier: ^13.0.0
         version: 13.0.0(encoding@0.1.13)
@@ -122,179 +110,135 @@ importers:
         specifier: ^7.0.3
         version: 7.0.3
       esbuild:
-        specifier: ^0.21.3
-        version: 0.21.3
+        specifier: ^0.25.5
+        version: 0.25.5
       esbuild-plugin-clean:
         specifier: ^1.0.1
-        version: 1.0.1(esbuild@0.21.3)
+        version: 1.0.1(esbuild@0.25.5)
       esbuild-plugin-copy:
         specifier: ^2.1.1
-        version: 2.1.1(esbuild@0.21.3)
+        version: 2.1.1(esbuild@0.25.5)
       eslint:
-        specifier: ^8.57.0
-        version: 8.57.0
-      eslint-config-love:
-        specifier: ^47.0.0
-        version: 47.0.0(@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-n@17.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5)
-      eslint-config-standard:
-        specifier: ^17.1.0
-        version: 17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-n@17.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0)
-      eslint-define-config:
-        specifier: ^2.1.0
-        version: 2.1.0
-      eslint-import-resolver-typescript:
-        specifier: ^3.6.1
-        version: 3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
-      eslint-plugin-import:
-        specifier: ^2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+        specifier: ^9.30.1
+        version: 9.30.1(jiti@2.4.2)
       eslint-plugin-jsdoc:
-        specifier: ^48.2.5
-        version: 48.2.5(eslint@8.57.0)
-      eslint-plugin-n:
-        specifier: ^17.7.0
-        version: 17.7.0(eslint@8.57.0)
-      eslint-plugin-simple-import-sort:
-        specifier: ^12.1.0
-        version: 12.1.0(eslint@8.57.0)
-      eslint-plugin-tsdoc:
-        specifier: ^0.2.17
-        version: 0.2.17
-      expect:
-        specifier: ^29.7.0
-        version: 29.7.0
+        specifier: ^51.3.3
+        version: 51.3.3(eslint@9.30.1(jiti@2.4.2))
+      eslint-plugin-perfectionist:
+        specifier: ^4.15.0
+        version: 4.15.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint-plugin-vue:
+        specifier: ^10.3.0
+        version: 10.3.0(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.30.1(jiti@2.4.2)))
       glob:
-        specifier: ^10.3.15
-        version: 10.3.15
+        specifier: ^11.0.3
+        version: 11.0.3
       husky:
-        specifier: ^9.0.11
-        version: 9.0.11
+        specifier: ^9.1.7
+        version: 9.1.7
       lint-staged:
-        specifier: ^15.2.2
-        version: 15.2.2
+        specifier: ^16.1.2
+        version: 16.1.2
+      neostandard:
+        specifier: ^0.12.2
+        version: 0.12.2(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
       prettier:
-        specifier: ^3.2.5
-        version: 3.2.5
-      release-it:
-        specifier: ^17.3.0
-        version: 17.3.0(typescript@5.4.5)
+        specifier: ^3.6.2
+        version: 3.6.2
       rimraf:
-        specifier: ^5.0.7
-        version: 5.0.7
+        specifier: ^6.0.1
+        version: 6.0.1
       semver:
         specifier: ^7.5.3
-        version: 7.6.2
+        version: 7.7.2
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5)
+        version: 10.9.2(@types/node@24.0.10)(typescript@5.8.3)
       tsx:
-        specifier: ^4.10.5
-        version: 4.10.5
+        specifier: ^4.20.3
+        version: 4.20.3
       typescript:
-        specifier: ~5.4.5
-        version: 5.4.5
+        specifier: ~5.8.3
+        version: 5.8.3
+      vue-eslint-parser:
+        specifier: ^10.2.0
+        version: 10.2.0(eslint@9.30.1(jiti@2.4.2))
+    optionalDependencies:
+      bufferutil:
+        specifier: ^4.0.9
+        version: 4.0.9
+      utf-8-validate:
+        specifier: ^6.0.5
+        version: 6.0.5
 
   ui/web:
     dependencies:
       finalhandler:
-        specifier: ^1.2.0
-        version: 1.2.0
+        specifier: ^2.1.0
+        version: 2.1.0
       serve-static:
-        specifier: ^1.15.0
-        version: 1.15.0
+        specifier: ^2.2.0
+        version: 2.2.0
       vue:
-        specifier: ^3.4.27
-        version: 3.4.27(typescript@5.4.5)
+        specifier: ^3.5.17
+        version: 3.5.17(typescript@5.8.3)
       vue-router:
-        specifier: ^4.3.2
-        version: 4.3.2(vue@3.4.27(typescript@5.4.5))
+        specifier: ^4.5.1
+        version: 4.5.1(vue@3.5.17(typescript@5.8.3))
       vue-toast-notification:
-        specifier: ^3.1.2
-        version: 3.1.2(vue@3.4.27(typescript@5.4.5))
+        specifier: ^3.1.3
+        version: 3.1.3(vue@3.5.17(typescript@5.8.3))
     devDependencies:
-      '@rushstack/eslint-patch':
-        specifier: ^1.10.3
-        version: 1.10.3
-      '@tsconfig/node20':
-        specifier: ^20.1.4
-        version: 20.1.4
+      '@tsconfig/node22':
+        specifier: ^22.0.2
+        version: 22.0.2
       '@types/jsdom':
-        specifier: ^21.1.6
-        version: 21.1.6
+        specifier: ^21.1.7
+        version: 21.1.7
       '@types/node':
-        specifier: ^20.12.12
-        version: 20.12.12
-      '@typescript-eslint/eslint-plugin':
-        specifier: ^7.10.0
-        version: 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/parser':
-        specifier: ^7.10.0
-        version: 7.10.0(eslint@8.57.0)(typescript@5.4.5)
+        specifier: ^24.0.10
+        version: 24.0.10
       '@vitejs/plugin-vue':
-        specifier: ^5.0.4
-        version: 5.0.4(vite@5.2.11(@types/node@20.12.12))(vue@3.4.27(typescript@5.4.5))
+        specifier: ^6.0.0
+        version: 6.0.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
       '@vitejs/plugin-vue-jsx':
-        specifier: ^3.1.0
-        version: 3.1.0(vite@5.2.11(@types/node@20.12.12))(vue@3.4.27(typescript@5.4.5))
+        specifier: ^5.0.1
+        version: 5.0.1(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))
       '@vitest/coverage-v8':
-        specifier: ^1.6.0
-        version: 1.6.0(vitest@1.6.0(@types/node@20.12.12)(jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)))
-      '@vue/eslint-config-prettier':
-        specifier: ^9.0.0
-        version: 9.0.0(@types/eslint@8.56.10)(eslint@8.57.0)(prettier@3.2.5)
-      '@vue/eslint-config-typescript':
-        specifier: ^13.0.0
-        version: 13.0.0(eslint-plugin-vue@9.26.0(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5)
+        specifier: ^3.2.4
+        version: 3.2.4(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(tsx@4.20.3)(yaml@2.8.0))
       '@vue/test-utils':
         specifier: ^2.4.6
         version: 2.4.6
       '@vue/tsconfig':
-        specifier: ^0.5.1
-        version: 0.5.1
+        specifier: ^0.7.0
+        version: 0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
       cross-env:
         specifier: ^7.0.3
         version: 7.0.3
-      eslint:
-        specifier: ^8.57.0
-        version: 8.57.0
-      eslint-define-config:
-        specifier: ^2.1.0
-        version: 2.1.0
-      eslint-import-resolver-typescript:
-        specifier: ^3.6.1
-        version: 3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
-      eslint-plugin-import:
-        specifier: ^2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
-      eslint-plugin-simple-import-sort:
-        specifier: ^12.1.0
-        version: 12.1.0(eslint@8.57.0)
-      eslint-plugin-vue:
-        specifier: ^9.26.0
-        version: 9.26.0(eslint@8.57.0)
       jsdom:
-        specifier: ^24.0.0
-        version: 24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+        specifier: ^26.1.0
+        version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
       prettier:
-        specifier: ^3.2.5
-        version: 3.2.5
+        specifier: ^3.6.2
+        version: 3.6.2
       rimraf:
-        specifier: ^5.0.7
-        version: 5.0.7
+        specifier: ^6.0.1
+        version: 6.0.1
       typescript:
-        specifier: ~5.4.5
-        version: 5.4.5
+        specifier: ~5.8.3
+        version: 5.8.3
       vite:
-        specifier: ^5.2.11
-        version: 5.2.11(@types/node@20.12.12)
+        specifier: ^7.0.2
+        version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
       vitest:
-        specifier: ^1.6.0
-        version: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: ^3.2.4
+        version: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(tsx@4.20.3)(yaml@2.8.0)
 
 packages:
 
-  0x@5.7.0:
-    resolution: {integrity: sha512-oc5lqaJP7lu3C5zx+MRbsigfRlTTUg0LKjFDCr0NmR9g+nkZcR7yRU6jzMOiedSVKZ0p0b4y2TBQ+YYo6O3sZg==}
+  0x@5.8.0:
+    resolution: {integrity: sha512-d07ToyEoxGz/u8JbaqeivEr3b2X6sPil6IDBINrXKBkgveiTW30EfjwDtHmE0TvP5Y4p5vE7ErbK3p68kYE0aw==}
     engines: {node: '>=8.5.0'}
     hasBin: true
 
@@ -302,152 +246,132 @@ packages:
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
 
+  '@asamuzakjp/css-color@3.2.0':
+    resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
   '@assemblyscript/loader@0.19.23':
     resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==}
 
-  '@babel/code-frame@7.24.2':
-    resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==}
+  '@babel/code-frame@7.27.1':
+    resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/compat-data@7.24.4':
-    resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==}
+  '@babel/compat-data@7.28.0':
+    resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/core@7.24.5':
-    resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==}
+  '@babel/core@7.28.0':
+    resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/generator@7.24.5':
-    resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==}
+  '@babel/generator@7.28.0':
+    resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-annotate-as-pure@7.22.5':
-    resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
+  '@babel/helper-annotate-as-pure@7.27.3':
+    resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-compilation-targets@7.23.6':
-    resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
+  '@babel/helper-compilation-targets@7.27.2':
+    resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-create-class-features-plugin@7.24.5':
-    resolution: {integrity: sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==}
+  '@babel/helper-create-class-features-plugin@7.27.1':
+    resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
 
-  '@babel/helper-environment-visitor@7.22.20':
-    resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-function-name@7.23.0':
-    resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+  '@babel/helper-globals@7.28.0':
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-hoist-variables@7.22.5':
-    resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+  '@babel/helper-member-expression-to-functions@7.27.1':
+    resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-member-expression-to-functions@7.24.5':
-    resolution: {integrity: sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==}
+  '@babel/helper-module-imports@7.27.1':
+    resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-module-imports@7.22.15':
-    resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-module-imports@7.24.3':
-    resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-module-transforms@7.24.5':
-    resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==}
+  '@babel/helper-module-transforms@7.27.3':
+    resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
 
-  '@babel/helper-optimise-call-expression@7.22.5':
-    resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
+  '@babel/helper-optimise-call-expression@7.27.1':
+    resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-plugin-utils@7.24.5':
-    resolution: {integrity: sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==}
+  '@babel/helper-plugin-utils@7.27.1':
+    resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-replace-supers@7.24.1':
-    resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==}
+  '@babel/helper-replace-supers@7.27.1':
+    resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
 
-  '@babel/helper-simple-access@7.24.5':
-    resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-skip-transparent-expression-wrappers@7.22.5':
-    resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-split-export-declaration@7.24.5':
-    resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-string-parser@7.24.1':
-    resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==}
+  '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+    resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-validator-identifier@7.24.5':
-    resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==}
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-validator-option@7.23.5':
-    resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
+  '@babel/helper-validator-identifier@7.27.1':
+    resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helpers@7.24.5':
-    resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==}
+  '@babel/helper-validator-option@7.27.1':
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/highlight@7.24.5':
-    resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==}
+  '@babel/helpers@7.27.6':
+    resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/parser@7.24.5':
-    resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==}
+  '@babel/parser@7.28.0':
+    resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  '@babel/plugin-syntax-jsx@7.24.1':
-    resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==}
+  '@babel/plugin-syntax-jsx@7.27.1':
+    resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-syntax-typescript@7.24.1':
-    resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==}
+  '@babel/plugin-syntax-typescript@7.27.1':
+    resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-typescript@7.24.5':
-    resolution: {integrity: sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==}
+  '@babel/plugin-transform-typescript@7.28.0':
+    resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/template@7.24.0':
-    resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
+  '@babel/template@7.27.2':
+    resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/traverse@7.24.5':
-    resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==}
+  '@babel/traverse@7.28.0':
+    resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/types@7.24.5':
-    resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==}
+  '@babel/types@7.28.0':
+    resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==}
     engines: {node: '>=6.9.0'}
 
-  '@bcoe/v8-coverage@0.2.3':
-    resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+  '@bcoe/v8-coverage@1.0.2':
+    resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
+    engines: {node: '>=18'}
 
   '@clinic/bubbleprof@10.0.0':
     resolution: {integrity: sha512-7Y0uYO4cz7+Y1advV891uMJLXbZMIriLsV1IHSSVJxmf8tEFm8vogKi/GdYyi4CY0D5heuqOFze/WNrv+U3LRw==}
@@ -480,400 +404,569 @@ packages:
     resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
     engines: {node: '>=0.1.90'}
 
-  '@commitlint/cli@19.3.0':
-    resolution: {integrity: sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g==}
+  '@commitlint/cli@19.8.1':
+    resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==}
     engines: {node: '>=v18'}
     hasBin: true
 
-  '@commitlint/config-conventional@19.2.2':
-    resolution: {integrity: sha512-mLXjsxUVLYEGgzbxbxicGPggDuyWNkf25Ht23owXIH+zV2pv1eJuzLK3t1gDY5Gp6pxdE60jZnWUY5cvgL3ufw==}
+  '@commitlint/config-conventional@19.8.1':
+    resolution: {integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==}
     engines: {node: '>=v18'}
 
-  '@commitlint/config-validator@19.0.3':
-    resolution: {integrity: sha512-2D3r4PKjoo59zBc2auodrSCaUnCSALCx54yveOFwwP/i2kfEAQrygwOleFWswLqK0UL/F9r07MFi5ev2ohyM4Q==}
+  '@commitlint/config-validator@19.8.1':
+    resolution: {integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==}
     engines: {node: '>=v18'}
 
-  '@commitlint/ensure@19.0.3':
-    resolution: {integrity: sha512-SZEpa/VvBLoT+EFZVb91YWbmaZ/9rPH3ESrINOl0HD2kMYsjvl0tF7nMHh0EpTcv4+gTtZBAe1y/SS6/OhfZzQ==}
+  '@commitlint/ensure@19.8.1':
+    resolution: {integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/execute-rule@19.0.0':
-    resolution: {integrity: sha512-mtsdpY1qyWgAO/iOK0L6gSGeR7GFcdW7tIjcNFxcWkfLDF5qVbPHKuGATFqRMsxcO8OUKNj0+3WOHB7EHm4Jdw==}
+  '@commitlint/execute-rule@19.8.1':
+    resolution: {integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==}
     engines: {node: '>=v18'}
 
-  '@commitlint/format@19.3.0':
-    resolution: {integrity: sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==}
+  '@commitlint/format@19.8.1':
+    resolution: {integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/is-ignored@19.2.2':
-    resolution: {integrity: sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==}
+  '@commitlint/is-ignored@19.8.1':
+    resolution: {integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==}
     engines: {node: '>=v18'}
 
-  '@commitlint/lint@19.2.2':
-    resolution: {integrity: sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==}
+  '@commitlint/lint@19.8.1':
+    resolution: {integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/load@19.2.0':
-    resolution: {integrity: sha512-XvxxLJTKqZojCxaBQ7u92qQLFMMZc4+p9qrIq/9kJDy8DOrEa7P1yx7Tjdc2u2JxIalqT4KOGraVgCE7eCYJyQ==}
+  '@commitlint/load@19.8.1':
+    resolution: {integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==}
     engines: {node: '>=v18'}
 
-  '@commitlint/message@19.0.0':
-    resolution: {integrity: sha512-c9czf6lU+9oF9gVVa2lmKaOARJvt4soRsVmbR7Njwp9FpbBgste5i7l/2l5o8MmbwGh4yE1snfnsy2qyA2r/Fw==}
+  '@commitlint/message@19.8.1':
+    resolution: {integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==}
     engines: {node: '>=v18'}
 
-  '@commitlint/parse@19.0.3':
-    resolution: {integrity: sha512-Il+tNyOb8VDxN3P6XoBBwWJtKKGzHlitEuXA5BP6ir/3loWlsSqDr5aecl6hZcC/spjq4pHqNh0qPlfeWu38QA==}
+  '@commitlint/parse@19.8.1':
+    resolution: {integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/read@19.2.1':
-    resolution: {integrity: sha512-qETc4+PL0EUv7Q36lJbPG+NJiBOGg7SSC7B5BsPWOmei+Dyif80ErfWQ0qXoW9oCh7GTpTNRoaVhiI8RbhuaNw==}
+  '@commitlint/read@19.8.1':
+    resolution: {integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==}
     engines: {node: '>=v18'}
 
-  '@commitlint/resolve-extends@19.1.0':
-    resolution: {integrity: sha512-z2riI+8G3CET5CPgXJPlzftH+RiWYLMYv4C9tSLdLXdr6pBNimSKukYP9MS27ejmscqCTVA4almdLh0ODD2KYg==}
+  '@commitlint/resolve-extends@19.8.1':
+    resolution: {integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==}
     engines: {node: '>=v18'}
 
-  '@commitlint/rules@19.0.3':
-    resolution: {integrity: sha512-TspKb9VB6svklxNCKKwxhELn7qhtY1rFF8ls58DcFd0F97XoG07xugPjjbVnLqmMkRjZDbDIwBKt9bddOfLaPw==}
+  '@commitlint/rules@19.8.1':
+    resolution: {integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/to-lines@19.0.0':
-    resolution: {integrity: sha512-vkxWo+VQU5wFhiP9Ub9Sre0FYe019JxFikrALVoD5UGa8/t3yOJEpEhxC5xKiENKKhUkTpEItMTRAjHw2SCpZw==}
+  '@commitlint/to-lines@19.8.1':
+    resolution: {integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==}
     engines: {node: '>=v18'}
 
-  '@commitlint/top-level@19.0.0':
-    resolution: {integrity: sha512-KKjShd6u1aMGNkCkaX4aG1jOGdn7f8ZI8TR1VEuNqUOjWTOdcDSsmglinglJ18JTjuBX5I1PtjrhQCRcixRVFQ==}
+  '@commitlint/top-level@19.8.1':
+    resolution: {integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==}
     engines: {node: '>=v18'}
 
-  '@commitlint/types@19.0.3':
-    resolution: {integrity: sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==}
+  '@commitlint/types@19.8.1':
+    resolution: {integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==}
     engines: {node: '>=v18'}
 
+  '@cspell/cspell-bundled-dicts@9.1.2':
+    resolution: {integrity: sha512-mdhxj7j1zqXYKO/KPx2MgN3RPAvqoWvncxz2dOMFBcuUteZPt58NenUoi0VZXEhV/FM2V80NvhHZZafaIcxVjQ==}
+    engines: {node: '>=20'}
+
+  '@cspell/cspell-pipe@9.1.2':
+    resolution: {integrity: sha512-/pIhsf4SI4Q/kvehq9GsGKLgbQsRhiDgthQIgO6YOrEa761wOI2hVdRyc0Tgc1iAGiJEedDaFsAhabVRJBeo2g==}
+    engines: {node: '>=20'}
+
+  '@cspell/cspell-resolver@9.1.2':
+    resolution: {integrity: sha512-dNDx7yMl2h1Ousk08lizTou+BUvce4RPSnPXrQPB7B7CscgZloSyuP3Yyj1Zt81pHNpggrym4Ezx6tMdyPjESw==}
+    engines: {node: '>=20'}
+
+  '@cspell/cspell-service-bus@9.1.2':
+    resolution: {integrity: sha512-YOsUctzCMzEJbKdzNyvPkyMen/i7sGO3Xgcczn848GJPlRsJc50QwsoU67SY7zEARz6y2WS0tv5F5RMrRO4idw==}
+    engines: {node: '>=20'}
+
+  '@cspell/cspell-types@9.1.2':
+    resolution: {integrity: sha512-bSDDjoQi4pbh1BULEA596XCo1PMShTpTb4J2lj8jVYqYgXYQNjSmQFA1fj4NHesC84JpK1um4ybzXBcqtniC7Q==}
+    engines: {node: '>=20'}
+
+  '@cspell/dict-ada@4.1.0':
+    resolution: {integrity: sha512-7SvmhmX170gyPd+uHXrfmqJBY5qLcCX8kTGURPVeGxmt8XNXT75uu9rnZO+jwrfuU2EimNoArdVy5GZRGljGNg==}
+
+  '@cspell/dict-al@1.1.0':
+    resolution: {integrity: sha512-PtNI1KLmYkELYltbzuoztBxfi11jcE9HXBHCpID2lou/J4VMYKJPNqe4ZjVzSI9NYbMnMnyG3gkbhIdx66VSXg==}
+
+  '@cspell/dict-aws@4.0.10':
+    resolution: {integrity: sha512-0qW4sI0GX8haELdhfakQNuw7a2pnWXz3VYQA2MpydH2xT2e6EN9DWFpKAi8DfcChm8MgDAogKkoHtIo075iYng==}
+
+  '@cspell/dict-bash@4.2.0':
+    resolution: {integrity: sha512-HOyOS+4AbCArZHs/wMxX/apRkjxg6NDWdt0jF9i9XkvJQUltMwEhyA2TWYjQ0kssBsnof+9amax2lhiZnh3kCg==}
+
+  '@cspell/dict-companies@3.2.1':
+    resolution: {integrity: sha512-ryaeJ1KhTTKL4mtinMtKn8wxk6/tqD4vX5tFP+Hg89SiIXmbMk5vZZwVf+eyGUWJOyw5A1CVj9EIWecgoi+jYQ==}
+
+  '@cspell/dict-cpp@6.0.8':
+    resolution: {integrity: sha512-BzurRZilWqaJt32Gif6/yCCPi+FtrchjmnehVEIFzbWyeBd/VOUw77IwrEzehZsu5cRU91yPWuWp5fUsKfDAXA==}
+
+  '@cspell/dict-cryptocurrencies@5.0.4':
+    resolution: {integrity: sha512-6iFu7Abu+4Mgqq08YhTKHfH59mpMpGTwdzDB2Y8bbgiwnGFCeoiSkVkgLn1Kel2++hYcZ8vsAW/MJS9oXxuMag==}
+
+  '@cspell/dict-csharp@4.0.6':
+    resolution: {integrity: sha512-w/+YsqOknjQXmIlWDRmkW+BHBPJZ/XDrfJhZRQnp0wzpPOGml7W0q1iae65P2AFRtTdPKYmvSz7AL5ZRkCnSIw==}
+
+  '@cspell/dict-css@4.0.17':
+    resolution: {integrity: sha512-2EisRLHk6X/PdicybwlajLGKF5aJf4xnX2uuG5lexuYKt05xV/J/OiBADmi8q9obhxf1nesrMQbqAt+6CsHo/w==}
+
+  '@cspell/dict-dart@2.3.0':
+    resolution: {integrity: sha512-1aY90lAicek8vYczGPDKr70pQSTQHwMFLbmWKTAI6iavmb1fisJBS1oTmMOKE4ximDf86MvVN6Ucwx3u/8HqLg==}
+
+  '@cspell/dict-data-science@2.0.8':
+    resolution: {integrity: sha512-uyAtT+32PfM29wRBeAkUSbkytqI8bNszNfAz2sGPtZBRmsZTYugKMEO9eDjAIE/pnT9CmbjNuoiXhk+Ss4fCOg==}
+
+  '@cspell/dict-django@4.1.4':
+    resolution: {integrity: sha512-fX38eUoPvytZ/2GA+g4bbdUtCMGNFSLbdJJPKX2vbewIQGfgSFJKY56vvcHJKAvw7FopjvgyS/98Ta9WN1gckg==}
+
+  '@cspell/dict-docker@1.1.14':
+    resolution: {integrity: sha512-p6Qz5mokvcosTpDlgSUREdSbZ10mBL3ndgCdEKMqjCSZJFdfxRdNdjrGER3lQ6LMq5jGr1r7nGXA0gvUJK80nw==}
+
+  '@cspell/dict-dotnet@5.0.9':
+    resolution: {integrity: sha512-JGD6RJW5sHtO5lfiJl11a5DpPN6eKSz5M1YBa1I76j4dDOIqgZB6rQexlDlK1DH9B06X4GdDQwdBfnpAB0r2uQ==}
+
+  '@cspell/dict-elixir@4.0.7':
+    resolution: {integrity: sha512-MAUqlMw73mgtSdxvbAvyRlvc3bYnrDqXQrx5K9SwW8F7fRYf9V4vWYFULh+UWwwkqkhX9w03ZqFYRTdkFku6uA==}
+
+  '@cspell/dict-en-common-misspellings@2.1.2':
+    resolution: {integrity: sha512-r74AObInM1XOUxd3lASnNZNDOIA9Bka7mBDTkvkOeCGoLQhn+Cr7h1889u4K07KHbecKMHP6zw5zQhkdocNzCw==}
+
+  '@cspell/dict-en-gb-mit@3.1.3':
+    resolution: {integrity: sha512-4aY8ySQxSNSRILtf9lJIfSR+su86u8VL6z41gOIhvLIvYnHMFiohV7ebM91GbtdZXBazL7zmGFcpm2EnBzewug==}
+
+  '@cspell/dict-en_us@4.4.13':
+    resolution: {integrity: sha512-6TEHCJKmRqq7fQI7090p+ju12vhuGcNkc6YfxHrcjO816m53VPVaS6IfG6+6OqelQiOMjr0ZD8IHcDIkwThSFw==}
+
+  '@cspell/dict-filetypes@3.0.12':
+    resolution: {integrity: sha512-+ds5wgNdlUxuJvhg8A1TjuSpalDFGCh7SkANCWvIplg6QZPXL4j83lqxP7PgjHpx7PsBUS7vw0aiHPjZy9BItw==}
+
+  '@cspell/dict-flutter@1.1.0':
+    resolution: {integrity: sha512-3zDeS7zc2p8tr9YH9tfbOEYfopKY/srNsAa+kE3rfBTtQERAZeOhe5yxrnTPoufctXLyuUtcGMUTpxr3dO0iaA==}
+
+  '@cspell/dict-fonts@4.0.4':
+    resolution: {integrity: sha512-cHFho4hjojBcHl6qxidl9CvUb492IuSk7xIf2G2wJzcHwGaCFa2o3gRcxmIg1j62guetAeDDFELizDaJlVRIOg==}
+
+  '@cspell/dict-fsharp@1.1.0':
+    resolution: {integrity: sha512-oguWmHhGzgbgbEIBKtgKPrFSVAFtvGHaQS0oj+vacZqMObwkapcTGu7iwf4V3Bc2T3caf0QE6f6rQfIJFIAVsw==}
+
+  '@cspell/dict-fullstack@3.2.6':
+    resolution: {integrity: sha512-cSaq9rz5RIU9j+0jcF2vnKPTQjxGXclntmoNp4XB7yFX2621PxJcekGjwf/lN5heJwVxGLL9toR0CBlGKwQBgA==}
+
+  '@cspell/dict-gaming-terms@1.1.1':
+    resolution: {integrity: sha512-tb8GFxjTLDQstkJcJ90lDqF4rKKlMUKs5/ewePN9P+PYRSehqDpLI5S5meOfPit8LGszeOrjUdBQ4zXo7NpMyQ==}
+
+  '@cspell/dict-git@3.0.6':
+    resolution: {integrity: sha512-nazfOqyxlBOQGgcur9ssEOEQCEZkH8vXfQe8SDEx8sCN/g0SFm8ktabgLVmBOXjy3RzjVNLlM2nBfRQ7e6+5hQ==}
+
+  '@cspell/dict-golang@6.0.22':
+    resolution: {integrity: sha512-FvV0m3Y0nUFxw36uDCD8UtfOPv4wsZnnlabNwB3xNZ2IBn0gBURuMUZywScb9sd2wXM8VFBRoU//tc6NQsOVOg==}
+
+  '@cspell/dict-google@1.0.8':
+    resolution: {integrity: sha512-BnMHgcEeaLyloPmBs8phCqprI+4r2Jb8rni011A8hE+7FNk7FmLE3kiwxLFrcZnnb7eqM0agW4zUaNoB0P+z8A==}
+
+  '@cspell/dict-haskell@4.0.5':
+    resolution: {integrity: sha512-s4BG/4tlj2pPM9Ha7IZYMhUujXDnI0Eq1+38UTTCpatYLbQqDwRFf2KNPLRqkroU+a44yTUAe0rkkKbwy4yRtQ==}
+
+  '@cspell/dict-html-symbol-entities@4.0.3':
+    resolution: {integrity: sha512-aABXX7dMLNFdSE8aY844X4+hvfK7977sOWgZXo4MTGAmOzR8524fjbJPswIBK7GaD3+SgFZ2yP2o0CFvXDGF+A==}
+
+  '@cspell/dict-html@4.0.11':
+    resolution: {integrity: sha512-QR3b/PB972SRQ2xICR1Nw/M44IJ6rjypwzA4jn+GH8ydjAX9acFNfc+hLZVyNe0FqsE90Gw3evLCOIF0vy1vQw==}
+
+  '@cspell/dict-java@5.0.11':
+    resolution: {integrity: sha512-T4t/1JqeH33Raa/QK/eQe26FE17eUCtWu+JsYcTLkQTci2dk1DfcIKo8YVHvZXBnuM43ATns9Xs0s+AlqDeH7w==}
+
+  '@cspell/dict-julia@1.1.0':
+    resolution: {integrity: sha512-CPUiesiXwy3HRoBR3joUseTZ9giFPCydSKu2rkh6I2nVjXnl5vFHzOMLXpbF4HQ1tH2CNfnDbUndxD+I+7eL9w==}
+
+  '@cspell/dict-k8s@1.0.11':
+    resolution: {integrity: sha512-8ojNwB5j4PfZ1Gq9n5c/HKJCtZD3h6+wFy+zpALpDWFFQ2qT22Be30+3PVd+G5gng8or0LeK8VgKKd0l1uKPTA==}
+
+  '@cspell/dict-kotlin@1.1.0':
+    resolution: {integrity: sha512-vySaVw6atY7LdwvstQowSbdxjXG6jDhjkWVWSjg1XsUckyzH1JRHXe9VahZz1i7dpoFEUOWQrhIe5B9482UyJQ==}
+
+  '@cspell/dict-latex@4.0.3':
+    resolution: {integrity: sha512-2KXBt9fSpymYHxHfvhUpjUFyzrmN4c4P8mwIzweLyvqntBT3k0YGZJSriOdjfUjwSygrfEwiuPI1EMrvgrOMJw==}
+
+  '@cspell/dict-lorem-ipsum@4.0.4':
+    resolution: {integrity: sha512-+4f7vtY4dp2b9N5fn0za/UR0kwFq2zDtA62JCbWHbpjvO9wukkbl4rZg4YudHbBgkl73HRnXFgCiwNhdIA1JPw==}
+
+  '@cspell/dict-lua@4.0.7':
+    resolution: {integrity: sha512-Wbr7YSQw+cLHhTYTKV6cAljgMgcY+EUAxVIZW3ljKswEe4OLxnVJ7lPqZF5JKjlXdgCjbPSimsHqyAbC5pQN/Q==}
+
+  '@cspell/dict-makefile@1.0.4':
+    resolution: {integrity: sha512-E4hG/c0ekPqUBvlkrVvzSoAA+SsDA9bLi4xSV3AXHTVru7Y2bVVGMPtpfF+fI3zTkww/jwinprcU1LSohI3ylw==}
+
+  '@cspell/dict-markdown@2.0.11':
+    resolution: {integrity: sha512-stZieFKJyMQbzKTVoalSx2QqCpB0j8nPJF/5x+sBnDIWgMC65jp8Wil+jccWh9/vnUVukP3Ejewven5NC7SWuQ==}
+    peerDependencies:
+      '@cspell/dict-css': ^4.0.17
+      '@cspell/dict-html': ^4.0.11
+      '@cspell/dict-html-symbol-entities': ^4.0.3
+      '@cspell/dict-typescript': ^3.2.2
+
+  '@cspell/dict-monkeyc@1.0.10':
+    resolution: {integrity: sha512-7RTGyKsTIIVqzbvOtAu6Z/lwwxjGRtY5RkKPlXKHEoEAgIXwfDxb5EkVwzGQwQr8hF/D3HrdYbRT8MFBfsueZw==}
+
+  '@cspell/dict-node@5.0.7':
+    resolution: {integrity: sha512-ZaPpBsHGQCqUyFPKLyCNUH2qzolDRm1/901IO8e7btk7bEDF56DN82VD43gPvD4HWz3yLs/WkcLa01KYAJpnOw==}
+
+  '@cspell/dict-npm@5.2.9':
+    resolution: {integrity: sha512-1uxRQ0LGPweRX8U9EEoU/tk5GGtTLAJT0BMmeHbe2AfzxX3nYSZtK/q52h9yg/wZLgvnFYzha2DL70uuT8oZuA==}
+
+  '@cspell/dict-php@4.0.14':
+    resolution: {integrity: sha512-7zur8pyncYZglxNmqsRycOZ6inpDoVd4yFfz1pQRe5xaRWMiK3Km4n0/X/1YMWhh3e3Sl/fQg5Axb2hlN68t1g==}
+
+  '@cspell/dict-powershell@5.0.14':
+    resolution: {integrity: sha512-ktjjvtkIUIYmj/SoGBYbr3/+CsRGNXGpvVANrY0wlm/IoGlGywhoTUDYN0IsGwI2b8Vktx3DZmQkfb3Wo38jBA==}
+
+  '@cspell/dict-public-licenses@2.0.13':
+    resolution: {integrity: sha512-1Wdp/XH1ieim7CadXYE7YLnUlW0pULEjVl9WEeziZw3EKCAw8ZI8Ih44m4bEa5VNBLnuP5TfqC4iDautAleQzQ==}
+
+  '@cspell/dict-python@4.2.18':
+    resolution: {integrity: sha512-hYczHVqZBsck7DzO5LumBLJM119a3F17aj8a7lApnPIS7cmEwnPc2eACNscAHDk7qAo2127oI7axUoFMe9/g1g==}
+
+  '@cspell/dict-r@2.1.0':
+    resolution: {integrity: sha512-k2512wgGG0lTpTYH9w5Wwco+lAMf3Vz7mhqV8+OnalIE7muA0RSuD9tWBjiqLcX8zPvEJr4LdgxVju8Gk3OKyA==}
+
+  '@cspell/dict-ruby@5.0.8':
+    resolution: {integrity: sha512-ixuTneU0aH1cPQRbWJvtvOntMFfeQR2KxT8LuAv5jBKqQWIHSxzGlp+zX3SVyoeR0kOWiu64/O5Yn836A5yMcQ==}
+
+  '@cspell/dict-rust@4.0.11':
+    resolution: {integrity: sha512-OGWDEEzm8HlkSmtD8fV3pEcO2XBpzG2XYjgMCJCRwb2gRKvR+XIm6Dlhs04N/K2kU+iH8bvrqNpM8fS/BFl0uw==}
+
+  '@cspell/dict-scala@5.0.7':
+    resolution: {integrity: sha512-yatpSDW/GwulzO3t7hB5peoWwzo+Y3qTc0pO24Jf6f88jsEeKmDeKkfgPbYuCgbE4jisGR4vs4+jfQZDIYmXPA==}
+
+  '@cspell/dict-shell@1.1.0':
+    resolution: {integrity: sha512-D/xHXX7T37BJxNRf5JJHsvziFDvh23IF/KvkZXNSh8VqcRdod3BAz9VGHZf6VDqcZXr1VRqIYR3mQ8DSvs3AVQ==}
+
+  '@cspell/dict-software-terms@5.1.2':
+    resolution: {integrity: sha512-MssT9yyInezB6mFqHTDNOIVjbMakORllIt7IJ91LrgiQOcDLzidR0gN9pE340s655TJ8U5MJNAfRfH0oRU14KQ==}
+
+  '@cspell/dict-sql@2.2.0':
+    resolution: {integrity: sha512-MUop+d1AHSzXpBvQgQkCiok8Ejzb+nrzyG16E8TvKL2MQeDwnIvMe3bv90eukP6E1HWb+V/MA/4pnq0pcJWKqQ==}
+
+  '@cspell/dict-svelte@1.0.6':
+    resolution: {integrity: sha512-8LAJHSBdwHCoKCSy72PXXzz7ulGROD0rP1CQ0StOqXOOlTUeSFaJJlxNYjlONgd2c62XBQiN2wgLhtPN+1Zv7Q==}
+
+  '@cspell/dict-swift@2.0.5':
+    resolution: {integrity: sha512-3lGzDCwUmnrfckv3Q4eVSW3sK3cHqqHlPprFJZD4nAqt23ot7fic5ALR7J4joHpvDz36nHX34TgcbZNNZOC/JA==}
+
+  '@cspell/dict-terraform@1.1.1':
+    resolution: {integrity: sha512-07KFDwCU7EnKl4hOZLsLKlj6Zceq/IsQ3LRWUyIjvGFfZHdoGtFdCp3ZPVgnFaAcd/DKv+WVkrOzUBSYqHopQQ==}
+
+  '@cspell/dict-typescript@3.2.2':
+    resolution: {integrity: sha512-H9Y+uUHsTIDFO/jdfUAcqmcd5osT+2DB5b0aRCHfLWN/twUbGn/1qq3b7YwEvttxKlYzWHU3uNFf+KfA93VY7w==}
+
+  '@cspell/dict-vue@3.0.4':
+    resolution: {integrity: sha512-0dPtI0lwHcAgSiQFx8CzvqjdoXROcH+1LyqgROCpBgppommWpVhbQ0eubnKotFEXgpUCONVkeZJ6Ql8NbTEu+w==}
+
+  '@cspell/dynamic-import@9.1.2':
+    resolution: {integrity: sha512-Kg22HCx5m0znVPLea2jRrvMnzHZAAzqcDr5g6Dbd4Pizs5b3SPQuRpFmYaDvKo26JNZnfRqA9eweiuE5aQAf2A==}
+    engines: {node: '>=20'}
+
+  '@cspell/eslint-plugin@9.1.2':
+    resolution: {integrity: sha512-UUCCBAyv3gTL1P19fX9C+cknkwCXHvnHUAaFBz25dX6PhJSPyYPmVdA8jm/2H6+GQYKBnHvWgfjkkiZgtqoQRA==}
+    engines: {node: '>=20'}
+    peerDependencies:
+      eslint: ^7 || ^8 || ^9
+
+  '@cspell/filetypes@9.1.2':
+    resolution: {integrity: sha512-j+6kDz3GbeYwwtlzVosqVaSiFGMhf0u3y8eAP3IV2bTelhP2ZiOLD+yNbAyYGao7p10/Sqv+Ri0yT7IsGLniww==}
+    engines: {node: '>=20'}
+
+  '@cspell/strong-weak-map@9.1.2':
+    resolution: {integrity: sha512-6X9oXnklvdt1pd0x0Mh6qXaaIRxjt0G50Xz5ZGm3wpAagv0MFvTThdmYVFfBuZ91x7fDT3u77y3d1uqdGQW1CA==}
+    engines: {node: '>=20'}
+
+  '@cspell/url@9.1.2':
+    resolution: {integrity: sha512-PMJBuLYQIdFnEfPHQXaVE5hHUkbbOxOIRmHyZwWEc9+79tIaIkiwLpjZvbm8p6f9WXAaESqXs/uK2tUC/bjwmw==}
+    engines: {node: '>=20'}
+
   '@cspotcode/source-map-support@0.8.1':
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
 
+  '@csstools/color-helpers@5.0.2':
+    resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
+    engines: {node: '>=18'}
+
+  '@csstools/css-calc@2.1.4':
+    resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-color-parser@3.0.10':
+    resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-parser-algorithms@3.0.5':
+    resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-tokenizer@3.0.4':
+    resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+    engines: {node: '>=18'}
+
   '@dabh/diagnostics@2.0.3':
     resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
 
-  '@es-joy/jsdoccomment@0.43.0':
-    resolution: {integrity: sha512-Q1CnsQrytI3TlCB1IVWXWeqUIPGVEKGaE7IbVdt13Nq/3i0JESAkQQERrfiQkmlpijl+++qyqPgaS31Bvc1jRQ==}
-    engines: {node: '>=16'}
+  '@emnapi/core@1.4.3':
+    resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
 
-  '@esbuild/aix-ppc64@0.20.2':
-    resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
-    engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [aix]
+  '@emnapi/runtime@1.4.3':
+    resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
 
-  '@esbuild/aix-ppc64@0.21.3':
-    resolution: {integrity: sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==}
-    engines: {node: '>=12'}
+  '@emnapi/wasi-threads@1.0.2':
+    resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==}
+
+  '@es-joy/jsdoccomment@0.52.0':
+    resolution: {integrity: sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==}
+    engines: {node: '>=20.11.0'}
+
+  '@esbuild/aix-ppc64@0.25.5':
+    resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [aix]
 
-  '@esbuild/android-arm64@0.20.2':
-    resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [android]
-
-  '@esbuild/android-arm64@0.21.3':
-    resolution: {integrity: sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm64@0.25.5':
+    resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [android]
 
-  '@esbuild/android-arm@0.20.2':
-    resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
-    engines: {node: '>=12'}
-    cpu: [arm]
-    os: [android]
-
-  '@esbuild/android-arm@0.21.3':
-    resolution: {integrity: sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm@0.25.5':
+    resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [android]
 
-  '@esbuild/android-x64@0.20.2':
-    resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [android]
-
-  '@esbuild/android-x64@0.21.3':
-    resolution: {integrity: sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==}
-    engines: {node: '>=12'}
+  '@esbuild/android-x64@0.25.5':
+    resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [android]
 
-  '@esbuild/darwin-arm64@0.20.2':
-    resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [darwin]
-
-  '@esbuild/darwin-arm64@0.21.3':
-    resolution: {integrity: sha512-U3fuQ0xNiAkXOmQ6w5dKpEvXQRSpHOnbw7gEfHCRXPeTKW9sBzVck6C5Yneb8LfJm0l6le4NQfkNPnWMSlTFUQ==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-arm64@0.25.5':
+    resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [darwin]
 
-  '@esbuild/darwin-x64@0.20.2':
-    resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [darwin]
-
-  '@esbuild/darwin-x64@0.21.3':
-    resolution: {integrity: sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-x64@0.25.5':
+    resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [darwin]
 
-  '@esbuild/freebsd-arm64@0.20.2':
-    resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [freebsd]
-
-  '@esbuild/freebsd-arm64@0.21.3':
-    resolution: {integrity: sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-arm64@0.25.5':
+    resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [freebsd]
 
-  '@esbuild/freebsd-x64@0.20.2':
-    resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [freebsd]
-
-  '@esbuild/freebsd-x64@0.21.3':
-    resolution: {integrity: sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-x64@0.25.5':
+    resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [freebsd]
 
-  '@esbuild/linux-arm64@0.20.2':
-    resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [linux]
-
-  '@esbuild/linux-arm64@0.21.3':
-    resolution: {integrity: sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm64@0.25.5':
+    resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [linux]
 
-  '@esbuild/linux-arm@0.20.2':
-    resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
-    engines: {node: '>=12'}
-    cpu: [arm]
-    os: [linux]
-
-  '@esbuild/linux-arm@0.21.3':
-    resolution: {integrity: sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm@0.25.5':
+    resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [linux]
 
-  '@esbuild/linux-ia32@0.20.2':
-    resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [linux]
-
-  '@esbuild/linux-ia32@0.21.3':
-    resolution: {integrity: sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ia32@0.25.5':
+    resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [linux]
 
-  '@esbuild/linux-loong64@0.20.2':
-    resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
-    engines: {node: '>=12'}
-    cpu: [loong64]
-    os: [linux]
-
-  '@esbuild/linux-loong64@0.21.3':
-    resolution: {integrity: sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-loong64@0.25.5':
+    resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
+    engines: {node: '>=18'}
     cpu: [loong64]
     os: [linux]
 
-  '@esbuild/linux-mips64el@0.20.2':
-    resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
-    engines: {node: '>=12'}
-    cpu: [mips64el]
-    os: [linux]
-
-  '@esbuild/linux-mips64el@0.21.3':
-    resolution: {integrity: sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-mips64el@0.25.5':
+    resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
+    engines: {node: '>=18'}
     cpu: [mips64el]
     os: [linux]
 
-  '@esbuild/linux-ppc64@0.20.2':
-    resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
-    engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [linux]
-
-  '@esbuild/linux-ppc64@0.21.3':
-    resolution: {integrity: sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ppc64@0.25.5':
+    resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [linux]
 
-  '@esbuild/linux-riscv64@0.20.2':
-    resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
-    engines: {node: '>=12'}
-    cpu: [riscv64]
-    os: [linux]
-
-  '@esbuild/linux-riscv64@0.21.3':
-    resolution: {integrity: sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-riscv64@0.25.5':
+    resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
+    engines: {node: '>=18'}
     cpu: [riscv64]
     os: [linux]
 
-  '@esbuild/linux-s390x@0.20.2':
-    resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
-    engines: {node: '>=12'}
-    cpu: [s390x]
-    os: [linux]
-
-  '@esbuild/linux-s390x@0.21.3':
-    resolution: {integrity: sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-s390x@0.25.5':
+    resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
+    engines: {node: '>=18'}
     cpu: [s390x]
     os: [linux]
 
-  '@esbuild/linux-x64@0.20.2':
-    resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [linux]
-
-  '@esbuild/linux-x64@0.21.3':
-    resolution: {integrity: sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-x64@0.25.5':
+    resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [linux]
 
-  '@esbuild/netbsd-x64@0.20.2':
-    resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
+  '@esbuild/netbsd-arm64@0.25.5':
+    resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
     os: [netbsd]
 
-  '@esbuild/netbsd-x64@0.21.3':
-    resolution: {integrity: sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==}
-    engines: {node: '>=12'}
+  '@esbuild/netbsd-x64@0.25.5':
+    resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [netbsd]
 
-  '@esbuild/openbsd-x64@0.20.2':
-    resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
+  '@esbuild/openbsd-arm64@0.25.5':
+    resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
     os: [openbsd]
 
-  '@esbuild/openbsd-x64@0.21.3':
-    resolution: {integrity: sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==}
-    engines: {node: '>=12'}
+  '@esbuild/openbsd-x64@0.25.5':
+    resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [openbsd]
 
-  '@esbuild/sunos-x64@0.20.2':
-    resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [sunos]
-
-  '@esbuild/sunos-x64@0.21.3':
-    resolution: {integrity: sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==}
-    engines: {node: '>=12'}
+  '@esbuild/sunos-x64@0.25.5':
+    resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [sunos]
 
-  '@esbuild/win32-arm64@0.20.2':
-    resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [win32]
-
-  '@esbuild/win32-arm64@0.21.3':
-    resolution: {integrity: sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-arm64@0.25.5':
+    resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [win32]
 
-  '@esbuild/win32-ia32@0.20.2':
-    resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [win32]
-
-  '@esbuild/win32-ia32@0.21.3':
-    resolution: {integrity: sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-ia32@0.25.5':
+    resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [win32]
 
-  '@esbuild/win32-x64@0.20.2':
-    resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [win32]
-
-  '@esbuild/win32-x64@0.21.3':
-    resolution: {integrity: sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-x64@0.25.5':
+    resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [win32]
 
-  '@eslint-community/eslint-utils@4.4.0':
-    resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+  '@eslint-community/eslint-utils@4.7.0':
+    resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
 
-  '@eslint-community/regexpp@4.10.0':
-    resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+  '@eslint-community/regexpp@4.12.1':
+    resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
     engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
 
-  '@eslint/eslintrc@2.1.4':
-    resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  '@eslint/config-array@0.21.0':
+    resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/js@8.57.0':
-    resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  '@eslint/config-helpers@0.3.0':
+    resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/core@0.14.0':
+    resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/core@0.15.1':
+    resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/eslintrc@3.3.1':
+    resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/js@9.30.1':
+    resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/object-schema@2.1.6':
+    resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/plugin-kit@0.3.3':
+    resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@gar/promisify@1.1.3':
     resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
 
-  '@humanwhocodes/config-array@0.11.14':
-    resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
-    engines: {node: '>=10.10.0'}
+  '@humanfs/core@0.19.1':
+    resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanfs/node@0.16.6':
+    resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanwhocodes/gitignore-to-minimatch@1.0.2':
+    resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==}
 
   '@humanwhocodes/module-importer@1.0.1':
     resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
     engines: {node: '>=12.22'}
 
-  '@humanwhocodes/object-schema@2.0.3':
-    resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+  '@humanwhocodes/retry@0.3.1':
+    resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+    engines: {node: '>=18.18'}
 
-  '@iarna/toml@2.2.5':
-    resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
+  '@humanwhocodes/retry@0.4.3':
+    resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+    engines: {node: '>=18.18'}
 
-  '@inquirer/figures@1.0.2':
-    resolution: {integrity: sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w==}
-    engines: {node: '>=18'}
+  '@isaacs/balanced-match@4.0.1':
+    resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
+    engines: {node: 20 || >=22}
+
+  '@isaacs/brace-expansion@5.0.0':
+    resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
+    engines: {node: 20 || >=22}
 
   '@isaacs/cliui@8.0.2':
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
@@ -890,84 +983,79 @@ packages:
   '@jercle/yargonaut@1.1.5':
     resolution: {integrity: sha512-zBp2myVvBHp1UaJsNTyS6q4UDKT7eRiqTS4oNTS6VQMd6mpxYOdbeK4pY279cDCdakGy6hG0J3ejoXZVsPwHqw==}
 
-  '@jest/expect-utils@29.7.0':
-    resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
-  '@jest/schemas@29.6.3':
-    resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
-  '@jest/types@29.6.3':
-    resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
-  '@jridgewell/gen-mapping@0.3.5':
-    resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
-    engines: {node: '>=6.0.0'}
+  '@jridgewell/gen-mapping@0.3.12':
+    resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
 
   '@jridgewell/resolve-uri@3.1.2':
     resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
     engines: {node: '>=6.0.0'}
 
-  '@jridgewell/set-array@1.2.1':
-    resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
-    engines: {node: '>=6.0.0'}
-
-  '@jridgewell/sourcemap-codec@1.4.15':
-    resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+  '@jridgewell/sourcemap-codec@1.5.4':
+    resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
 
-  '@jridgewell/trace-mapping@0.3.25':
-    resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+  '@jridgewell/trace-mapping@0.3.29':
+    resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
 
   '@jridgewell/trace-mapping@0.3.9':
     resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
 
-  '@ljharb/through@2.3.13':
-    resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==}
-    engines: {node: '>= 0.4'}
+  '@jsr/std__assert@1.0.13':
+    resolution: {integrity: sha512-rZ44REoi2/p+gqu8OfkcNeaTOSiG1kD6v8gyA0YjkXsOkDsiGw9g8h7JuGC/OD7GgOVgTEY+9Cih49Y18rkrCQ==, tarball: https://npm.jsr.io/~/11/@jsr/std__assert/1.0.13.tgz}
 
-  '@microsoft/tsdoc-config@0.16.2':
-    resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==}
+  '@jsr/std__expect@1.0.16':
+    resolution: {integrity: sha512-ni+i1uwOnU9PMOcSQAHsNTYYD5o137871udJi9hiGneXlZTrD4/fVcNJ5AyH+GEF5yZzjeCjfaeHdXLFKZFCfQ==, tarball: https://npm.jsr.io/~/11/@jsr/std__expect/1.0.16.tgz}
 
-  '@microsoft/tsdoc@0.14.2':
-    resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
+  '@jsr/std__internal@1.0.9':
+    resolution: {integrity: sha512-s+f4qrJzZgPAy7XuFOtgaSaxyPLnnEmAfXGLvRXGxPTL76URLVHkF+hOzqXz+bmk8/awybF6BRsasxtAQOV23Q==, tarball: https://npm.jsr.io/~/11/@jsr/std__internal/1.0.9.tgz}
 
-  '@mikro-orm/cli@6.2.7':
-    resolution: {integrity: sha512-P+ifBdGkQbdot6oABh7w6IT30Il1O6BQMDWhy/VvdvucO0FosaDtF64GAnpAqoweUpxlVDyD4Osg0AZwblGDbw==}
+  '@mikro-orm/cli@6.4.16':
+    resolution: {integrity: sha512-O1uP+5j/xMYR8aPTamzQKYJnL1GCootGLR5UVH1bQJS9/ukFuAKDsu6VpJkJxE9j0TLxAhTmo56iZFUIS04sFQ==}
     engines: {node: '>= 18.12.0'}
     hasBin: true
 
-  '@mikro-orm/core@6.2.7':
-    resolution: {integrity: sha512-XTgeNsah4QpiYs0t/AaTjlpnBMEHx8rCvn6uPlg2nONYzGHHL7L28WvTCMsMsmj7V8U8NhTEu2dQhma/OXfViA==}
+  '@mikro-orm/core@6.4.16':
+    resolution: {integrity: sha512-BW/My1VlI0R25Eojdh0UiET8J+mEruThksGVfllEnjJQ0uwGs3mm/TbMclv0g+d7Qp4+mpzDQU4+lIo8okHYsw==}
     engines: {node: '>= 18.12.0'}
 
-  '@mikro-orm/knex@6.2.7':
-    resolution: {integrity: sha512-gndT5begmE0hcK5i1w7jMRclFMobkXRD6kfu1h76lrBUmNFJqLYip8o11W2X7IIxE6gxv4Z23DFl4/7d9fvTww==}
+  '@mikro-orm/knex@6.4.16':
+    resolution: {integrity: sha512-2xQodj3uh8maPGsLR7PZMmECtdyLutGxzzrGrrhSX2b+7v9k4MeMazTi4/627hJvivdUpuofypW5zMpw3TU1vQ==}
     engines: {node: '>= 18.12.0'}
     peerDependencies:
       '@mikro-orm/core': ^6.0.0
+      better-sqlite3: '*'
+      libsql: '*'
+      mariadb: '*'
+    peerDependenciesMeta:
+      better-sqlite3:
+        optional: true
+      libsql:
+        optional: true
+      mariadb:
+        optional: true
 
-  '@mikro-orm/mariadb@6.2.7':
-    resolution: {integrity: sha512-9bAXoJOc4GrYdN7zx3uh3WAlpIj7mqNPhH3C/AACYhoi/MqdGET732Kkoa0fDM6YPMQrhFZUvRxykPf92A6NwQ==}
+  '@mikro-orm/mariadb@6.4.16':
+    resolution: {integrity: sha512-J9Hr6pkqZh287YIl2NsKGs932EYPPQU0pSES0UYORO56o8g1mGqAsLnYQL0T6fy4UuptUnJKSjHvjpLpKd/pcw==}
     engines: {node: '>= 18.12.0'}
     peerDependencies:
       '@mikro-orm/core': ^6.0.0
 
-  '@mikro-orm/reflection@6.2.7':
-    resolution: {integrity: sha512-piJKaUlgRVvNjCYYuxzMYKUezw8eXljKoIvHKPjOlZAVzeTNVYzDhHyahXACuXwrpjJaAFfBZ5EKUkJZ3v56/w==}
+  '@mikro-orm/reflection@6.4.16':
+    resolution: {integrity: sha512-waPaDZwOlwNUUuD00rj8HrByhs1y5hvTEFIc14LX0mgtP+OHkarM7+JTJ68RB2UaDVyZ/Bmof9uhH7TOH2awVw==}
     engines: {node: '>= 18.12.0'}
     peerDependencies:
       '@mikro-orm/core': ^6.0.0
 
-  '@mikro-orm/sqlite@6.2.7':
-    resolution: {integrity: sha512-WnPbBWv8sHwxdPmCux1GvtnRN7au1QcSMQ/20atqyC51EpIA5y2sH4B3PkRezL5Cil6E+G6dfiqJscJvaa5CKw==}
+  '@mikro-orm/sqlite@6.4.16':
+    resolution: {integrity: sha512-mwmv+ZkZiA8HMt+UvA7jI/3qv4R3Sz1zOcLfAo9QMov2DdnPxJYkDxQ+9fAnrswurOzoo0ApqUGZ4hF6J0djfw==}
     engines: {node: '>= 18.12.0'}
     peerDependencies:
       '@mikro-orm/core': ^6.0.0
 
-  '@mongodb-js/saslprep@1.1.7':
-    resolution: {integrity: sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==}
+  '@mongodb-js/saslprep@1.3.0':
+    resolution: {integrity: sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==}
+
+  '@napi-rs/wasm-runtime@0.2.11':
+    resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==}
 
   '@nearform/heap-profiler@2.0.0':
     resolution: {integrity: sha512-846CWyq3Ky5rzcl8Z3S+VT3z6GQSlYD1G/dqbtANu29NUHoCO+W7tOZRK6eA6FjLHnNX0DvP1Mrt2oFBPnkxLw==}
@@ -978,72 +1066,24 @@ packages:
     engines: {node: '>= 8'}
 
   '@nodelib/fs.stat@2.0.5':
-    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
-    engines: {node: '>= 8'}
-
-  '@nodelib/fs.walk@1.2.8':
-    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
-    engines: {node: '>= 8'}
-
-  '@npmcli/fs@1.1.1':
-    resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
-
-  '@npmcli/move-file@1.1.2':
-    resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
-    engines: {node: '>=10'}
-    deprecated: This functionality has been moved to @npmcli/fs
-
-  '@octokit/auth-token@4.0.0':
-    resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==}
-    engines: {node: '>= 18'}
-
-  '@octokit/core@5.2.0':
-    resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==}
-    engines: {node: '>= 18'}
-
-  '@octokit/endpoint@9.0.5':
-    resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==}
-    engines: {node: '>= 18'}
-
-  '@octokit/graphql@7.1.0':
-    resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==}
-    engines: {node: '>= 18'}
-
-  '@octokit/openapi-types@22.2.0':
-    resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==}
-
-  '@octokit/plugin-paginate-rest@11.3.1':
-    resolution: {integrity: sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==}
-    engines: {node: '>= 18'}
-    peerDependencies:
-      '@octokit/core': '5'
-
-  '@octokit/plugin-request-log@4.0.1':
-    resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==}
-    engines: {node: '>= 18'}
-    peerDependencies:
-      '@octokit/core': '5'
-
-  '@octokit/plugin-rest-endpoint-methods@13.2.2':
-    resolution: {integrity: sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==}
-    engines: {node: '>= 18'}
-    peerDependencies:
-      '@octokit/core': ^5
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
 
-  '@octokit/request-error@5.1.0':
-    resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==}
-    engines: {node: '>= 18'}
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
 
-  '@octokit/request@8.4.0':
-    resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==}
-    engines: {node: '>= 18'}
+  '@nolyfill/is-core-module@1.0.39':
+    resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
+    engines: {node: '>=12.4.0'}
 
-  '@octokit/rest@20.1.1':
-    resolution: {integrity: sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==}
-    engines: {node: '>= 18'}
+  '@npmcli/fs@1.1.1':
+    resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
 
-  '@octokit/types@13.5.0':
-    resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==}
+  '@npmcli/move-file@1.1.2':
+    resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
+    engines: {node: '>=10'}
+    deprecated: This functionality has been moved to @npmcli/fs
 
   '@one-ini/wasm@0.1.1':
     resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
@@ -1052,121 +1092,125 @@ packages:
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
 
-  '@pkgr/core@0.1.1':
-    resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
+  '@pkgr/core@0.2.7':
+    resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==}
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
 
-  '@pnpm/config.env-replace@1.1.0':
-    resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==}
-    engines: {node: '>=12.22.0'}
-
-  '@pnpm/network.ca-file@1.0.2':
-    resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==}
-    engines: {node: '>=12.22.0'}
-
-  '@pnpm/npm-conf@2.2.2':
-    resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==}
-    engines: {node: '>=12'}
+  '@rolldown/pluginutils@1.0.0-beta.19':
+    resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==}
 
-  '@release-it/bumper@6.0.1':
-    resolution: {integrity: sha512-yeQsbGNMzzN0c/5JV1awXP6UHX/kJamXCKR6/daS0YQfj98SZXAcLn3JEq+qfK/Jq/cnATnlz5r6UY0cfBkm1A==}
-    engines: {node: '>=18'}
-    peerDependencies:
-      release-it: ^17.0.0
+  '@rolldown/pluginutils@1.0.0-beta.23':
+    resolution: {integrity: sha512-lLCP4LUecUGBLq8EfkbY2esGYyvZj5ee+WZG12+mVnQH48b46SVbwp+0vJkD+6Pnsc+u9SWarBV9sQ5mVwmb5g==}
 
-  '@rollup/rollup-android-arm-eabi@4.17.2':
-    resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==}
+  '@rollup/rollup-android-arm-eabi@4.44.1':
+    resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.17.2':
-    resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==}
+  '@rollup/rollup-android-arm64@4.44.1':
+    resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.17.2':
-    resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==}
+  '@rollup/rollup-darwin-arm64@4.44.1':
+    resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.17.2':
-    resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==}
+  '@rollup/rollup-darwin-x64@4.44.1':
+    resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
-    resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==}
+  '@rollup/rollup-freebsd-arm64@4.44.1':
+    resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.44.1':
+    resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.44.1':
+    resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.17.2':
-    resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==}
+  '@rollup/rollup-linux-arm-musleabihf@4.44.1':
+    resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.17.2':
-    resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==}
+  '@rollup/rollup-linux-arm64-gnu@4.44.1':
+    resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.17.2':
-    resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==}
+  '@rollup/rollup-linux-arm64-musl@4.44.1':
+    resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
-    resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==}
+  '@rollup/rollup-linux-loongarch64-gnu@4.44.1':
+    resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==}
+    cpu: [loong64]
+    os: [linux]
+
+  '@rollup/rollup-linux-powerpc64le-gnu@4.44.1':
+    resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.17.2':
-    resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==}
+  '@rollup/rollup-linux-riscv64-gnu@4.44.1':
+    resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.17.2':
-    resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==}
+  '@rollup/rollup-linux-riscv64-musl@4.44.1':
+    resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@rollup/rollup-linux-s390x-gnu@4.44.1':
+    resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.17.2':
-    resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==}
+  '@rollup/rollup-linux-x64-gnu@4.44.1':
+    resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.17.2':
-    resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==}
+  '@rollup/rollup-linux-x64-musl@4.44.1':
+    resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.17.2':
-    resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==}
+  '@rollup/rollup-win32-arm64-msvc@4.44.1':
+    resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.17.2':
-    resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==}
+  '@rollup/rollup-win32-ia32-msvc@4.44.1':
+    resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.17.2':
-    resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==}
+  '@rollup/rollup-win32-x64-msvc@4.44.1':
+    resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==}
     cpu: [x64]
     os: [win32]
 
-  '@rushstack/eslint-patch@1.10.3':
-    resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
-
-  '@sinclair/typebox@0.27.8':
-    resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
-
   '@sindresorhus/is@5.6.0':
     resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
     engines: {node: '>=14.16'}
 
-  '@sindresorhus/merge-streams@2.3.0':
-    resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
-    engines: {node: '>=18'}
+  '@stylistic/eslint-plugin@2.11.0':
+    resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: '>=8.40.0'
 
   '@szmarczak/http-timer@5.0.1':
     resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
@@ -1186,11 +1230,8 @@ packages:
     resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
     engines: {node: '>= 6'}
 
-  '@tootallnate/quickjs-emscripten@0.23.0':
-    resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
-
-  '@ts-morph/common@0.23.0':
-    resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==}
+  '@ts-morph/common@0.27.0':
+    resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
 
   '@tsconfig/node10@1.0.11':
     resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -1204,20 +1245,26 @@ packages:
   '@tsconfig/node16@1.0.4':
     resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
 
-  '@tsconfig/node20@20.1.4':
-    resolution: {integrity: sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==}
+  '@tsconfig/node22@22.0.2':
+    resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==}
+
+  '@tybys/wasm-util@0.9.0':
+    resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
 
-  '@types/conventional-commits-parser@5.0.0':
-    resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
+  '@types/chai@5.2.2':
+    resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
 
-  '@types/eslint@8.56.10':
-    resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
+  '@types/conventional-commits-parser@5.0.1':
+    resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
 
-  '@types/estree@1.0.5':
-    resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+  '@types/deep-eql@4.0.2':
+    resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
 
-  '@types/geojson@7946.0.14':
-    resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  '@types/geojson@7946.0.16':
+    resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
 
   '@types/http-cache-semantics@4.0.4':
     resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
@@ -1225,26 +1272,20 @@ packages:
   '@types/istanbul-lib-coverage@2.0.6':
     resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
 
-  '@types/istanbul-lib-report@3.0.3':
-    resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
-
-  '@types/istanbul-reports@3.0.4':
-    resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
-
-  '@types/jsdom@21.1.6':
-    resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
+  '@types/jsdom@21.1.7':
+    resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
 
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/json5@0.0.29':
-    resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
-
   '@types/long@4.0.2':
     resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
 
-  '@types/node@20.12.12':
-    resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
+  '@types/node@22.16.0':
+    resolution: {integrity: sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ==}
+
+  '@types/node@24.0.10':
+    resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==}
 
   '@types/offscreencanvas@2019.3.0':
     resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
@@ -1252,11 +1293,8 @@ packages:
   '@types/seedrandom@2.4.34':
     resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==}
 
-  '@types/semver@7.5.8':
-    resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
-
-  '@types/stack-utils@2.0.3':
-    resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+  '@types/semver@7.7.0':
+    resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
 
   '@types/tough-cookie@4.0.5':
     resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
@@ -1273,180 +1311,276 @@ packages:
   '@types/whatwg-url@11.0.5':
     resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
 
-  '@types/ws@8.5.10':
-    resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
+  '@types/ws@8.18.1':
+    resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
 
-  '@types/yargs-parser@21.0.3':
-    resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
-
-  '@types/yargs@17.0.32':
-    resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==}
+  '@typescript-eslint/eslint-plugin@8.35.1':
+    resolution: {integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^8.35.1
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '>=4.8.4 <5.9.0'
 
-  '@typescript-eslint/eslint-plugin@7.10.0':
-    resolution: {integrity: sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/parser@8.35.1':
+    resolution: {integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      '@typescript-eslint/parser': ^7.0.0
-      eslint: ^8.56.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '>=4.8.4 <5.9.0'
 
-  '@typescript-eslint/parser@7.10.0':
-    resolution: {integrity: sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/project-service@8.35.1':
+    resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.56.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: '>=4.8.4 <5.9.0'
 
-  '@typescript-eslint/scope-manager@7.10.0':
-    resolution: {integrity: sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/scope-manager@8.35.1':
+    resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/type-utils@7.10.0':
-    resolution: {integrity: sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/tsconfig-utils@8.35.1':
+    resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.56.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: '>=4.8.4 <5.9.0'
+
+  '@typescript-eslint/type-utils@8.35.1':
+    resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '>=4.8.4 <5.9.0'
 
-  '@typescript-eslint/types@7.10.0':
-    resolution: {integrity: sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/types@8.35.1':
+    resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/typescript-estree@7.10.0':
-    resolution: {integrity: sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/typescript-estree@8.35.1':
+    resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+      typescript: '>=4.8.4 <5.9.0'
 
-  '@typescript-eslint/utils@7.10.0':
-    resolution: {integrity: sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@typescript-eslint/utils@8.35.1':
+    resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^8.56.0
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '>=4.8.4 <5.9.0'
+
+  '@typescript-eslint/visitor-keys@8.35.1':
+    resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/visitor-keys@7.10.0':
-    resolution: {integrity: sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==}
-    engines: {node: ^18.18.0 || >=20.0.0}
+  '@unrs/resolver-binding-android-arm-eabi@1.10.1':
+    resolution: {integrity: sha512-zohDKXT1Ok0yhbVGff4YAg9HUs5ietG5GpvJBPFSApZnGe7uf2cd26DRhKZbn0Be6xHUZrSzP+RAgMmzyc71EA==}
+    cpu: [arm]
+    os: [android]
 
-  '@ungap/structured-clone@1.2.0':
-    resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+  '@unrs/resolver-binding-android-arm64@1.10.1':
+    resolution: {integrity: sha512-tAN6k5UrTd4nicpA7s2PbjR/jagpDzAmvXFjbpTazUe5FRsFxVcBlS1F5Lzp5jtWU6bdiqRhSvd4X8rdpCffeA==}
+    cpu: [arm64]
+    os: [android]
 
-  '@vitejs/plugin-vue-jsx@3.1.0':
-    resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
-    engines: {node: ^14.18.0 || >=16.0.0}
+  '@unrs/resolver-binding-darwin-arm64@1.10.1':
+    resolution: {integrity: sha512-+FCsag8WkauI4dQ50XumCXdfvDCZEpMUnvZDsKMxfOisnEklpDFXc6ThY0WqybBYZbiwR5tWcFaZmI0G6b4vrg==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@unrs/resolver-binding-darwin-x64@1.10.1':
+    resolution: {integrity: sha512-qYKGGm5wk71ONcXTMZ0+J11qQeOAPz3nw6VtqrBUUELRyXFyvK8cHhHsLBFR4GHnilc2pgY1HTB2TvdW9wO26Q==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@unrs/resolver-binding-freebsd-x64@1.10.1':
+    resolution: {integrity: sha512-hOHMAhbvIQ63gkpgeNsXcWPSyvXH7ZEyeg254hY0Lp/hX8NdW+FsUWq73g9946Pc/BrcVI/I3C1cmZ4RCX9bNw==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@unrs/resolver-binding-linux-arm-gnueabihf@1.10.1':
+    resolution: {integrity: sha512-6ds7+zzHJgTDmpe0gmFcOTvSUhG5oZukkt+cCsSb3k4Uiz2yEQB4iCRITX2hBwSW+p8gAieAfecITjgqCkswXw==}
+    cpu: [arm]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-arm-musleabihf@1.10.1':
+    resolution: {integrity: sha512-P7A0G2/jW00diNJyFeq4W9/nxovD62Ay8CMP4UK9OymC7qO7rG1a8Upad68/bdfpIOn7KSp7Aj/6lEW3yyznAA==}
+    cpu: [arm]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-arm64-gnu@1.10.1':
+    resolution: {integrity: sha512-Cg6xzdkrpltcTPO4At+A79zkC7gPDQIgosJmVV8M104ImB6KZi1MrNXgDYIAfkhUYjPzjNooEDFRAwwPadS7ZA==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-arm64-musl@1.10.1':
+    resolution: {integrity: sha512-aNeg99bVkXa4lt+oZbjNRPC8ZpjJTKxijg/wILrJdzNyAymO2UC/HUK1UfDjt6T7U5p/mK24T3CYOi3/+YEQSA==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-ppc64-gnu@1.10.1':
+    resolution: {integrity: sha512-ylz5ojeXrkPrtnzVhpCO+YegG63/aKhkoTlY8PfMfBfLaUG8v6m6iqrL7sBUKdVBgOB4kSTUPt9efQdA/Y3Z/w==}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-riscv64-gnu@1.10.1':
+    resolution: {integrity: sha512-xcWyhmJfXXOxK7lvE4+rLwBq+on83svlc0AIypfe6x4sMJR+S4oD7n9OynaQShfj2SufPw2KJAotnsNb+4nN2g==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-riscv64-musl@1.10.1':
+    resolution: {integrity: sha512-mW9JZAdOCyorgi1eLJr4gX7xS67WNG9XNPYj5P8VuttK72XNsmdw9yhOO4tDANMgiLXFiSFaiL1gEpoNtRPw/A==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-s390x-gnu@1.10.1':
+    resolution: {integrity: sha512-NZGKhBy6xkJ0k09cWNZz4DnhBcGlhDd3W+j7EYoNvf5TSwj2K6kbmfqTWITEgkvjsMUjm1wsrc4IJaH6VtjyHQ==}
+    cpu: [s390x]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-x64-gnu@1.10.1':
+    resolution: {integrity: sha512-VsjgckJ0gNMw7p0d8In6uPYr+s0p16yrT2rvG4v2jUpEMYkpnfnCiALa9SWshbvlGjKQ98Q2x19agm3iFk8w8Q==}
+    cpu: [x64]
+    os: [linux]
+
+  '@unrs/resolver-binding-linux-x64-musl@1.10.1':
+    resolution: {integrity: sha512-idMnajMeejnaFi0Mx9UTLSYFDAOTfAEP7VjXNgxKApso3Eu2Njs0p2V95nNIyFi4oQVGFmIuCkoznAXtF/Zbmw==}
+    cpu: [x64]
+    os: [linux]
+
+  '@unrs/resolver-binding-wasm32-wasi@1.10.1':
+    resolution: {integrity: sha512-7jyhjIRNFjzlr8x5pth6Oi9hv3a7ubcVYm2GBFinkBQKcFhw4nIs5BtauSNtDW1dPIGrxF0ciynCZqzxMrYMsg==}
+    engines: {node: '>=14.0.0'}
+    cpu: [wasm32]
+
+  '@unrs/resolver-binding-win32-arm64-msvc@1.10.1':
+    resolution: {integrity: sha512-TY79+N+Gkoo7E99K+zmsKNeiuNJYlclZJtKqsHSls8We2iGhgxtletVsiBYie93MSTDRDMI8pkBZJlIJSZPrdA==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@unrs/resolver-binding-win32-ia32-msvc@1.10.1':
+    resolution: {integrity: sha512-BAJN5PEPlEV+1m8+PCtFoKm3LQ1P57B4Z+0+efU0NzmCaGk7pUaOxuPgl+m3eufVeeNBKiPDltG0sSB9qEfCxw==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@unrs/resolver-binding-win32-x64-msvc@1.10.1':
+    resolution: {integrity: sha512-2v3erKKmmCyIVvvhI2nF15qEbdBpISTq44m9pyd5gfIJB1PN94oePTLWEd82XUbIbvKhv76xTSeUQSCOGesLeg==}
+    cpu: [x64]
+    os: [win32]
+
+  '@vitejs/plugin-vue-jsx@5.0.1':
+    resolution: {integrity: sha512-X7qmQMXbdDh+sfHUttXokPD0cjPkMFoae7SgbkF9vi3idGUKmxLcnU2Ug49FHwiKXebfzQRIm5yK3sfCJzNBbg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
     peerDependencies:
-      vite: ^4.0.0 || ^5.0.0
+      vite: ^5.0.0 || ^6.0.0 || ^7.0.0
       vue: ^3.0.0
 
-  '@vitejs/plugin-vue@5.0.4':
-    resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  '@vitejs/plugin-vue@6.0.0':
+    resolution: {integrity: sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==}
+    engines: {node: ^20.19.0 || >=22.12.0}
     peerDependencies:
-      vite: ^5.0.0
+      vite: ^5.0.0 || ^6.0.0 || ^7.0.0
       vue: ^3.2.25
 
-  '@vitest/coverage-v8@1.6.0':
-    resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==}
+  '@vitest/coverage-v8@3.2.4':
+    resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
+    peerDependencies:
+      '@vitest/browser': 3.2.4
+      vitest: 3.2.4
+    peerDependenciesMeta:
+      '@vitest/browser':
+        optional: true
+
+  '@vitest/expect@3.2.4':
+    resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
+
+  '@vitest/mocker@3.2.4':
+    resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
     peerDependencies:
-      vitest: 1.6.0
+      msw: ^2.4.9
+      vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+    peerDependenciesMeta:
+      msw:
+        optional: true
+      vite:
+        optional: true
 
-  '@vitest/expect@1.6.0':
-    resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
+  '@vitest/pretty-format@3.2.4':
+    resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
 
-  '@vitest/runner@1.6.0':
-    resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
+  '@vitest/runner@3.2.4':
+    resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
 
-  '@vitest/snapshot@1.6.0':
-    resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==}
+  '@vitest/snapshot@3.2.4':
+    resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
 
-  '@vitest/spy@1.6.0':
-    resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
+  '@vitest/spy@3.2.4':
+    resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
 
-  '@vitest/utils@1.6.0':
-    resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
+  '@vitest/utils@3.2.4':
+    resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
 
-  '@vue/babel-helper-vue-transform-on@1.2.2':
-    resolution: {integrity: sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==}
+  '@vue/babel-helper-vue-transform-on@1.4.0':
+    resolution: {integrity: sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==}
 
-  '@vue/babel-plugin-jsx@1.2.2':
-    resolution: {integrity: sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==}
+  '@vue/babel-plugin-jsx@1.4.0':
+    resolution: {integrity: sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     peerDependenciesMeta:
       '@babel/core':
         optional: true
 
-  '@vue/babel-plugin-resolve-type@1.2.2':
-    resolution: {integrity: sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==}
+  '@vue/babel-plugin-resolve-type@1.4.0':
+    resolution: {integrity: sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@vue/compiler-core@3.4.27':
-    resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==}
-
-  '@vue/compiler-dom@3.4.27':
-    resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==}
+  '@vue/compiler-core@3.5.17':
+    resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==}
 
-  '@vue/compiler-sfc@3.4.27':
-    resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==}
+  '@vue/compiler-dom@3.5.17':
+    resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==}
 
-  '@vue/compiler-ssr@3.4.27':
-    resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==}
+  '@vue/compiler-sfc@3.5.17':
+    resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==}
 
-  '@vue/devtools-api@6.6.1':
-    resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
-
-  '@vue/eslint-config-prettier@9.0.0':
-    resolution: {integrity: sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==}
-    peerDependencies:
-      eslint: '>= 8.0.0'
-      prettier: '>= 3.0.0'
+  '@vue/compiler-ssr@3.5.17':
+    resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==}
 
-  '@vue/eslint-config-typescript@13.0.0':
-    resolution: {integrity: sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==}
-    engines: {node: ^18.18.0 || >=20.0.0}
-    peerDependencies:
-      eslint: ^8.56.0
-      eslint-plugin-vue: ^9.0.0
-      typescript: '>=4.7.4'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+  '@vue/devtools-api@6.6.4':
+    resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
 
-  '@vue/reactivity@3.4.27':
-    resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==}
+  '@vue/reactivity@3.5.17':
+    resolution: {integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==}
 
-  '@vue/runtime-core@3.4.27':
-    resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==}
+  '@vue/runtime-core@3.5.17':
+    resolution: {integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==}
 
-  '@vue/runtime-dom@3.4.27':
-    resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==}
+  '@vue/runtime-dom@3.5.17':
+    resolution: {integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==}
 
-  '@vue/server-renderer@3.4.27':
-    resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==}
+  '@vue/server-renderer@3.5.17':
+    resolution: {integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==}
     peerDependencies:
-      vue: 3.4.27
+      vue: 3.5.17
 
-  '@vue/shared@3.4.27':
-    resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
+  '@vue/shared@3.5.17':
+    resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==}
 
   '@vue/test-utils@2.4.6':
     resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
 
-  '@vue/tsconfig@0.5.1':
-    resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
+  '@vue/tsconfig@0.7.0':
+    resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==}
+    peerDependencies:
+      typescript: 5.x
+      vue: ^3.4.0
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+      vue:
+        optional: true
 
   '@webgpu/types@0.1.16':
     resolution: {integrity: sha512-9E61voMP4+Rze02jlTXud++Htpjyyk8vw5Hyw9FGRrmhHQg2GqbuOfwf5Klrb8vTxc2XWI3EfO7RUHMpxTj26A==}
@@ -1478,8 +1612,8 @@ packages:
     resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
     engines: {node: '>=0.4.0'}
 
-  acorn-walk@8.3.2:
-    resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
+  acorn-walk@8.3.4:
+    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
     engines: {node: '>=0.4.0'}
 
   acorn@7.4.1:
@@ -1487,8 +1621,8 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  acorn@8.11.3:
-    resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+  acorn@8.15.0:
+    resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
     engines: {node: '>=0.4.0'}
     hasBin: true
 
@@ -1496,12 +1630,12 @@ packages:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
 
-  agent-base@7.1.1:
-    resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
+  agent-base@7.1.3:
+    resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
     engines: {node: '>= 14'}
 
-  agentkeepalive@4.5.0:
-    resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
+  agentkeepalive@4.6.0:
+    resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
     engines: {node: '>= 8.0.0'}
 
   aggregate-error@3.1.0:
@@ -1527,8 +1661,8 @@ packages:
   ajv@6.12.6:
     resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
 
-  ajv@8.13.0:
-    resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==}
+  ajv@8.17.1:
+    resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
 
   ansi-align@3.0.1:
     resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
@@ -1537,13 +1671,9 @@ packages:
     resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==}
     engines: {node: '>=4'}
 
-  ansi-escapes@4.3.2:
-    resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
-    engines: {node: '>=8'}
-
-  ansi-escapes@6.2.1:
-    resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==}
-    engines: {node: '>=14.16'}
+  ansi-escapes@7.0.0:
+    resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
+    engines: {node: '>=18'}
 
   ansi-regex@2.1.1:
     resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
@@ -1561,8 +1691,8 @@ packages:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
 
-  ansi-regex@6.0.1:
-    resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
+  ansi-regex@6.1.0:
+    resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
     engines: {node: '>=12'}
 
   ansi-styles@2.2.1:
@@ -1577,10 +1707,6 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
-  ansi-styles@5.2.0:
-    resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
-    engines: {node: '>=10'}
-
   ansi-styles@6.2.1:
     resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
     engines: {node: '>=12'}
@@ -1610,8 +1736,8 @@ packages:
   argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
-  array-buffer-byte-length@1.0.1:
-    resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
+  array-buffer-byte-length@1.0.2:
+    resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
     engines: {node: '>= 0.4'}
 
   array-flatten@3.0.0:
@@ -1623,32 +1749,35 @@ packages:
   array-ify@1.0.0:
     resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
 
-  array-includes@3.1.8:
-    resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
+  array-includes@3.1.9:
+    resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
     engines: {node: '>= 0.4'}
 
+  array-timsort@1.0.3:
+    resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
+
   array-union@2.1.0:
     resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
     engines: {node: '>=8'}
 
-  array.prototype.findlastindex@1.2.5:
-    resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==}
+  array.prototype.findlast@1.2.5:
+    resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
     engines: {node: '>= 0.4'}
 
-  array.prototype.flat@1.3.2:
-    resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+  array.prototype.flat@1.3.3:
+    resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
     engines: {node: '>= 0.4'}
 
-  array.prototype.flatmap@1.3.2:
-    resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+  array.prototype.flatmap@1.3.3:
+    resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
     engines: {node: '>= 0.4'}
 
-  array.prototype.map@1.0.7:
-    resolution: {integrity: sha512-XpcFfLoBEAhezrrNw1V+yLXkE7M6uR7xJEsxbG6c/V9v043qurwVJB9r9UTnoSioFDoz1i1VOydpWGmJpfVZbg==}
+  array.prototype.tosorted@1.1.4:
+    resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
     engines: {node: '>= 0.4'}
 
-  arraybuffer.prototype.slice@1.0.3:
-    resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
+  arraybuffer.prototype.slice@1.0.4:
+    resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
     engines: {node: '>= 0.4'}
 
   asn1.js@4.10.1:
@@ -1664,21 +1793,22 @@ packages:
   assert@1.5.1:
     resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==}
 
-  assertion-error@1.1.0:
-    resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+  assertion-error@2.0.1:
+    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+    engines: {node: '>=12'}
 
-  ast-types@0.13.4:
-    resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
-    engines: {node: '>=4'}
+  ast-v8-to-istanbul@0.3.3:
+    resolution: {integrity: sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==}
 
-  async-retry@1.3.3:
-    resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
+  async-function@1.0.0:
+    resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+    engines: {node: '>= 0.4'}
 
   async@2.6.4:
     resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
 
-  async@3.2.5:
-    resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
+  async@3.2.6:
+    resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
 
   asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -1691,11 +1821,6 @@ packages:
     resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
     engines: {node: '>=10.12.0'}
 
-  auto-changelog@2.4.0:
-    resolution: {integrity: sha512-vh17hko1c0ItsEcw6m7qPRf3m45u+XK5QyCrrBFViElZ8jnKrPC1roSznrd1fIB/0vR/zawdECCRJtTuqIXaJw==}
-    engines: {node: '>=8.3'}
-    hasBin: true
-
   autocannon@7.15.0:
     resolution: {integrity: sha512-NaP2rQyA+tcubOJMFv2+oeW9jv2pq/t+LM6BL3cfJic0HEfscEcnWgAyU5YovE/oTHUzAgTliGdLPR+RQAWUbg==}
     hasBin: true
@@ -1707,11 +1832,11 @@ packages:
   aws-sign2@0.7.0:
     resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
 
-  aws4@1.13.0:
-    resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==}
+  aws4@1.13.2:
+    resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
 
-  b4a@1.6.6:
-    resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==}
+  b4a@1.6.7:
+    resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
 
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1726,9 +1851,6 @@ packages:
   bcrypt-pbkdf@1.0.2:
     resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
 
-  before-after-hook@2.2.3:
-    resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
-
   binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
@@ -1742,11 +1864,11 @@ packages:
   bl@4.1.0:
     resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
 
-  bn.js@4.12.0:
-    resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
+  bn.js@4.12.2:
+    resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==}
 
-  bn.js@5.2.1:
-    resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==}
+  bn.js@5.2.2:
+    resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==}
 
   boolbase@1.0.0:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -1755,15 +1877,11 @@ packages:
     resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
     engines: {node: '>=10'}
 
-  boxen@7.1.1:
-    resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==}
-    engines: {node: '>=14.16'}
-
-  brace-expansion@1.1.11:
-    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+  brace-expansion@1.1.12:
+    resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
 
-  brace-expansion@2.0.1:
-    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+  brace-expansion@2.0.2:
+    resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
 
   braces@3.0.3:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -1795,8 +1913,9 @@ packages:
   browserify-des@1.0.2:
     resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==}
 
-  browserify-rsa@4.1.0:
-    resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==}
+  browserify-rsa@4.1.1:
+    resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==}
+    engines: {node: '>= 0.10'}
 
   browserify-sign@4.2.3:
     resolution: {integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==}
@@ -1805,18 +1924,18 @@ packages:
   browserify-zlib@0.2.0:
     resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==}
 
-  browserify@17.0.0:
-    resolution: {integrity: sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==}
+  browserify@17.0.1:
+    resolution: {integrity: sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==}
     engines: {node: '>= 0.8'}
     hasBin: true
 
-  browserslist@4.23.0:
-    resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
+  browserslist@4.25.1:
+    resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
-  bson@6.7.0:
-    resolution: {integrity: sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==}
+  bson@6.10.4:
+    resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==}
     engines: {node: '>=16.20.1'}
 
   buffer-equal@0.0.1:
@@ -1835,25 +1954,22 @@ packages:
   buffer@5.7.1:
     resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
 
-  bufferutil@4.0.8:
-    resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
+  bufferutil@4.0.9:
+    resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
     engines: {node: '>=6.14.2'}
 
-  builtin-modules@3.3.0:
-    resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
-    engines: {node: '>=6'}
-
   builtin-status-codes@3.0.0:
     resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
 
-  bundle-name@4.1.0:
-    resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+  c8@10.1.3:
+    resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==}
     engines: {node: '>=18'}
-
-  c8@9.1.0:
-    resolution: {integrity: sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==}
-    engines: {node: '>=14.14.0'}
     hasBin: true
+    peerDependencies:
+      monocart-coverage-reports: ^2
+    peerDependenciesMeta:
+      monocart-coverage-reports:
+        optional: true
 
   cac@6.7.14:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
@@ -1874,8 +1990,16 @@ packages:
   cached-path-relative@1.1.0:
     resolution: {integrity: sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==}
 
-  call-bind@1.0.7:
-    resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+  call-bind-apply-helpers@1.0.2:
+    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+    engines: {node: '>= 0.4'}
+
+  call-bind@1.0.8:
+    resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+    engines: {node: '>= 0.4'}
+
+  call-bound@1.0.4:
+    resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
     engines: {node: '>= 0.4'}
 
   callsites@3.1.0:
@@ -1893,12 +2017,8 @@ packages:
     resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
     engines: {node: '>=10'}
 
-  camelcase@7.0.1:
-    resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
-    engines: {node: '>=14.16'}
-
-  caniuse-lite@1.0.30001620:
-    resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==}
+  caniuse-lite@1.0.30001726:
+    resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==}
 
   caseless@0.12.0:
     resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
@@ -1906,9 +2026,9 @@ packages:
   cephes@2.0.0:
     resolution: {integrity: sha512-4GMUzkcXHZ0HMZ3gZdBrv8pQs1/zkJh2Q9rQOF8NJZHanM359y3XOSdeqmDBPfxQKYQpJt58R3dUpofrIXJ2mg==}
 
-  chai@4.4.1:
-    resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
-    engines: {node: '>=4'}
+  chai@5.2.0:
+    resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
+    engines: {node: '>=12'}
 
   chalk@1.1.3:
     resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
@@ -1922,8 +2042,8 @@ packages:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
-  chalk@5.3.0:
-    resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
+  chalk@5.4.1:
+    resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
     engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
 
   char-spinner@1.0.1:
@@ -1932,8 +2052,9 @@ packages:
   chardet@0.7.0:
     resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
 
-  check-error@1.0.3:
-    resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+  check-error@2.1.1:
+    resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+    engines: {node: '>= 16'}
 
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
@@ -1953,25 +2074,22 @@ packages:
   ci-info@2.0.0:
     resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
 
-  ci-info@3.9.0:
-    resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
-    engines: {node: '>=8'}
-
-  cipher-base@1.0.4:
-    resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==}
+  cipher-base@1.0.6:
+    resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==}
+    engines: {node: '>= 0.10'}
 
   clean-stack@2.2.0:
     resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
     engines: {node: '>=6'}
 
+  clear-module@4.1.2:
+    resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==}
+    engines: {node: '>=8'}
+
   cli-boxes@2.2.1:
     resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
     engines: {node: '>=6'}
 
-  cli-boxes@3.0.0:
-    resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
-    engines: {node: '>=10'}
-
   cli-cursor@2.1.0:
     resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==}
     engines: {node: '>=4'}
@@ -1980,9 +2098,9 @@ packages:
     resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
     engines: {node: '>=8'}
 
-  cli-cursor@4.0.0:
-    resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  cli-cursor@5.0.0:
+    resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+    engines: {node: '>=18'}
 
   cli-spinners@2.9.2:
     resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
@@ -1999,10 +2117,6 @@ packages:
   cli-width@2.2.1:
     resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==}
 
-  cli-width@4.1.0:
-    resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
-    engines: {node: '>= 12'}
-
   clinic@13.0.0:
     resolution: {integrity: sha512-QAD3cLgA1OqEC7fJSDbAt4U0BKGAK1c5yopN5tu1OtmmsbRHHYxBeSMiElQfuMMdbkAuLEE7HOffZ0hKMzaYVw==}
     hasBin: true
@@ -2021,8 +2135,8 @@ packages:
     resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
     engines: {node: '>=0.8'}
 
-  code-block-writer@13.0.1:
-    resolution: {integrity: sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==}
+  code-block-writer@13.0.3:
+    resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
 
   code-point-at@1.1.0:
     resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
@@ -2071,16 +2185,16 @@ packages:
     resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
     engines: {node: '>=14'}
 
-  commander@11.1.0:
-    resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
-    engines: {node: '>=16'}
+  commander@14.0.0:
+    resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
+    engines: {node: '>=20'}
 
   commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
-  commander@7.2.0:
-    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
-    engines: {node: '>= 10'}
+  comment-json@4.2.5:
+    resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==}
+    engines: {node: '>= 6'}
 
   comment-parser@1.4.1:
     resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
@@ -2107,9 +2221,6 @@ packages:
     resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
     engines: {node: '>=12'}
 
-  confbox@0.1.7:
-    resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
-
   config-chain@1.1.13:
     resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
 
@@ -2117,10 +2228,6 @@ packages:
     resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
     engines: {node: '>=8'}
 
-  configstore@6.0.0:
-    resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==}
-    engines: {node: '>=12'}
-
   console-browserify@1.2.0:
     resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
 
@@ -2161,13 +2268,13 @@ packages:
   core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
-  cosmiconfig-typescript-loader@5.0.0:
-    resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
-    engines: {node: '>=v16'}
+  cosmiconfig-typescript-loader@6.1.0:
+    resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==}
+    engines: {node: '>=v18'}
     peerDependencies:
       '@types/node': '*'
-      cosmiconfig: '>=8.2'
-      typescript: '>=4'
+      cosmiconfig: '>=9'
+      typescript: '>=5'
 
   cosmiconfig@9.0.0:
     resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
@@ -2181,6 +2288,9 @@ packages:
   create-ecdh@4.0.4:
     resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==}
 
+  create-hash@1.1.3:
+    resolution: {integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==}
+
   create-hash@1.2.0:
     resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
 
@@ -2201,28 +2311,54 @@ packages:
     engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
     hasBin: true
 
-  cross-spawn@7.0.3:
-    resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
 
-  crypto-browserify@3.12.0:
-    resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==}
+  crypto-browserify@3.12.1:
+    resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==}
+    engines: {node: '>= 0.10'}
 
   crypto-random-string@2.0.0:
     resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
     engines: {node: '>=8'}
 
-  crypto-random-string@4.0.0:
-    resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==}
-    engines: {node: '>=12'}
+  cspell-config-lib@9.1.2:
+    resolution: {integrity: sha512-QvHHGUuMI5h3ymU6O/Qz8zfhMhvPTuopT1FgebYRBB1cyggl4KnEJKU9m7wy/SQ1IGSlFDtQp6rCy70ujTfavQ==}
+    engines: {node: '>=20'}
+
+  cspell-dictionary@9.1.2:
+    resolution: {integrity: sha512-Osn5f9ugkX/zA3PVtSmYKRer3gZX3YqVB0UH0wVNzi8Ryl/1RUuYLIcvd0SDEhiVW56WKxFLfZ5sggTz/l9cDA==}
+    engines: {node: '>=20'}
+
+  cspell-glob@9.1.2:
+    resolution: {integrity: sha512-l7Mqirn5h2tilTXgRamRIqqnzeA7R5iJEtJkY/zHDMEBeLWTR/5ai7dBp2+ooe8gIebpDtvv4938IXa5/75E6g==}
+    engines: {node: '>=20'}
+
+  cspell-grammar@9.1.2:
+    resolution: {integrity: sha512-vUcnlUqJKK0yhwYHfGC71zjGyEn918l64U/NWb1ijn1VXrL6gsh3w8Acwdo++zbpOASd9HTAuuZelveDJKLLgA==}
+    engines: {node: '>=20'}
+    hasBin: true
+
+  cspell-io@9.1.2:
+    resolution: {integrity: sha512-oLPxbteI+uFV9ZPcJjII7Lr/C/gVXpdmDLlAMwR8/7LHGnEfxXR0lqYu5GZVEvZ7riX9whCUOsQWQQqr2u2Fzw==}
+    engines: {node: '>=20'}
+
+  cspell-lib@9.1.2:
+    resolution: {integrity: sha512-OFCssgfp6Z2gd1K8j2FsYr9YGoA/C6xXlcUwgU75Ut/XMZ/S44chdA9fUupGd4dUOw+CZl0qKzSP21J6kYObIw==}
+    engines: {node: '>=20'}
+
+  cspell-trie-lib@9.1.2:
+    resolution: {integrity: sha512-TkIQaknRRusUznqy+HwpqKCETCAznrzPJJHRHi8m6Zo3tAMsnIpaBQPRN8xem6w8/r/yJqFhLrsLSma0swyviQ==}
+    engines: {node: '>=20'}
 
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
     hasBin: true
 
-  cssstyle@4.0.1:
-    resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
+  cssstyle@4.6.0:
+    resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
     engines: {node: '>=18'}
 
   csstype@3.1.3:
@@ -2314,35 +2450,27 @@ packages:
     resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
     engines: {node: '>=0.10'}
 
-  data-uri-to-buffer@4.0.1:
-    resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
-    engines: {node: '>= 12'}
-
-  data-uri-to-buffer@6.0.2:
-    resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
-    engines: {node: '>= 14'}
-
   data-urls@5.0.0:
     resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
     engines: {node: '>=18'}
 
-  data-view-buffer@1.0.1:
-    resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
+  data-view-buffer@1.0.2:
+    resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
     engines: {node: '>= 0.4'}
 
-  data-view-byte-length@1.0.1:
-    resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==}
+  data-view-byte-length@1.0.2:
+    resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
     engines: {node: '>= 0.4'}
 
-  data-view-byte-offset@1.0.0:
-    resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==}
+  data-view-byte-offset@1.0.1:
+    resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
     engines: {node: '>= 0.4'}
 
-  dataloader@2.2.2:
-    resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==}
+  dataloader@2.2.3:
+    resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==}
 
-  date-fns@3.6.0:
-    resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+  date-fns@4.1.0:
+    resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
 
   debounce-fn@4.0.0:
     resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
@@ -2351,24 +2479,25 @@ packages:
   debounce@1.2.1:
     resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
 
-  debug@2.6.9:
-    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+  debug@3.2.7:
+    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
     peerDependencies:
       supports-color: '*'
     peerDependenciesMeta:
       supports-color:
         optional: true
 
-  debug@3.2.7:
-    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+  debug@4.3.4:
+    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+    engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
     peerDependenciesMeta:
       supports-color:
         optional: true
 
-  debug@4.3.4:
-    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+  debug@4.4.1:
+    resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
     engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
@@ -2380,15 +2509,15 @@ packages:
     resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
     engines: {node: '>=0.10.0'}
 
-  decimal.js@10.4.3:
-    resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
+  decimal.js@10.5.0:
+    resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
 
   decompress-response@6.0.0:
     resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
     engines: {node: '>=10'}
 
-  deep-eql@4.1.3:
-    resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
+  deep-eql@5.0.2:
+    resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
     engines: {node: '>=6'}
 
   deep-extend@0.6.0:
@@ -2398,14 +2527,6 @@ packages:
   deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
 
-  default-browser-id@5.0.0:
-    resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
-    engines: {node: '>=18'}
-
-  default-browser@5.2.1:
-    resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
-    engines: {node: '>=18'}
-
   defaults@1.0.4:
     resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
 
@@ -2417,10 +2538,6 @@ packages:
     resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
     engines: {node: '>= 0.4'}
 
-  define-lazy-prop@3.0.0:
-    resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
-    engines: {node: '>=12'}
-
   define-properties@1.2.1:
     resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
     engines: {node: '>= 0.4'}
@@ -2428,10 +2545,6 @@ packages:
   defined@1.0.1:
     resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
 
-  degenerator@5.0.1:
-    resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
-    engines: {node: '>= 14'}
-
   del@6.1.1:
     resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==}
     engines: {node: '>=10'}
@@ -2451,9 +2564,6 @@ packages:
     resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
     engines: {node: '>= 0.8'}
 
-  deprecation@2.3.1:
-    resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
-
   deps-sort@2.0.1:
     resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==}
     hasBin: true
@@ -2461,16 +2571,8 @@ packages:
   des.js@1.1.0:
     resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==}
 
-  destroy@1.2.0:
-    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
-    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
-
-  detect-indent@7.0.1:
-    resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==}
-    engines: {node: '>=12.20'}
-
-  detect-libc@2.0.3:
-    resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
+  detect-libc@2.0.4:
+    resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
     engines: {node: '>=8'}
 
   detective@5.2.1:
@@ -2478,10 +2580,6 @@ packages:
     engines: {node: '>=0.8.0'}
     hasBin: true
 
-  diff-sequences@29.6.3:
-    resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
   diff@4.0.2:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
@@ -2500,10 +2598,6 @@ packages:
     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
     engines: {node: '>=0.10.0'}
 
-  doctrine@3.0.0:
-    resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
-    engines: {node: '>=6.0.0'}
-
   domain-browser@1.2.0:
     resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
     engines: {node: '>=0.4', npm: '>=1.2'}
@@ -2516,10 +2610,14 @@ packages:
     resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
     engines: {node: '>=10'}
 
-  dotenv@16.4.5:
-    resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
+  dotenv@16.5.0:
+    resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==}
     engines: {node: '>=12'}
 
+  dunder-proto@1.0.1:
+    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+    engines: {node: '>= 0.4'}
+
   dup@1.0.0:
     resolution: {integrity: sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA==}
 
@@ -2543,14 +2641,14 @@ packages:
   ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
 
-  electron-to-chromium@1.4.776:
-    resolution: {integrity: sha512-s694bi3+gUzlliqxjPHpa9NRTlhzTgB34aan+pVKZmOTGy2xoZXl+8E1B8i5p5rtev3PKMK/H4asgNejC+YHNg==}
+  electron-to-chromium@1.5.179:
+    resolution: {integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==}
 
-  elliptic@6.5.5:
-    resolution: {integrity: sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==}
+  elliptic@6.6.1:
+    resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
 
-  emoji-regex@10.3.0:
-    resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
+  emoji-regex@10.4.0:
+    resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
 
   emoji-regex@7.0.3:
     resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}
@@ -2564,71 +2662,85 @@ packages:
   enabled@2.0.0:
     resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
 
-  encodeurl@1.0.2:
-    resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+  encodeurl@2.0.0:
+    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
     engines: {node: '>= 0.8'}
 
   encoding@0.1.13:
     resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
 
-  end-of-stream@1.4.4:
-    resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+  end-of-stream@1.4.5:
+    resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
 
   endpoint@0.4.5:
     resolution: {integrity: sha512-oA2ALUF+d4Y0I8/WMV/0BuAZGHxfIdAygr9ZXP4rfzmp5zpYZmYKHKAbqRQnrE1YGdPhVg4D24CQkyx2qYEoHg==}
 
-  enhanced-resolve@5.16.1:
-    resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==}
+  enhanced-resolve@5.18.2:
+    resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
     engines: {node: '>=10.13.0'}
 
   entities@4.5.0:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
 
+  entities@6.0.1:
+    resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+    engines: {node: '>=0.12'}
+
   env-paths@2.2.1:
     resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
     engines: {node: '>=6'}
 
+  env-paths@3.0.0:
+    resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
   env-string@1.0.1:
     resolution: {integrity: sha512-/DhCJDf5DSFK32joQiWRpWrT0h7p3hVQfMKxiBb7Nt8C8IF8BYyPtclDnuGGLOoj16d/8udKeiE7JbkotDmorQ==}
 
+  environment@1.1.0:
+    resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
+    engines: {node: '>=18'}
+
   err-code@2.0.3:
     resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
 
   error-ex@1.3.2:
     resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
 
-  es-abstract@1.23.3:
-    resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==}
+  es-abstract@1.24.0:
+    resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
     engines: {node: '>= 0.4'}
 
-  es-array-method-boxes-properly@1.0.0:
-    resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
-
-  es-define-property@1.0.0:
-    resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+  es-define-property@1.0.1:
+    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
     engines: {node: '>= 0.4'}
 
   es-errors@1.3.0:
     resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
     engines: {node: '>= 0.4'}
 
-  es-get-iterator@1.1.3:
-    resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
+  es-iterator-helpers@1.2.1:
+    resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
+    engines: {node: '>= 0.4'}
+
+  es-module-lexer@1.7.0:
+    resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
 
-  es-object-atoms@1.0.0:
-    resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
+  es-object-atoms@1.1.1:
+    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
     engines: {node: '>= 0.4'}
 
-  es-set-tostringtag@2.0.3:
-    resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
+  es-set-tostringtag@2.1.0:
+    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
     engines: {node: '>= 0.4'}
 
-  es-shim-unscopables@1.0.2:
-    resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+  es-shim-unscopables@1.1.0:
+    resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+    engines: {node: '>= 0.4'}
 
-  es-to-primitive@1.2.1:
-    resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+  es-to-primitive@1.3.0:
+    resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
     engines: {node: '>= 0.4'}
 
   es5-ext@0.10.64:
@@ -2659,28 +2771,19 @@ packages:
     peerDependencies:
       esbuild: '>= 0.14.0'
 
-  esbuild@0.20.2:
-    resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
-    engines: {node: '>=12'}
-    hasBin: true
-
-  esbuild@0.21.3:
-    resolution: {integrity: sha512-Kgq0/ZsAPzKrbOjCQcjoSmPoWhlcVnGAUo7jvaLHoxW1Drto0KGkR1xBNg2Cp43b9ImvxmPEJZ9xkfcnqPsfBw==}
-    engines: {node: '>=12'}
+  esbuild@0.25.5:
+    resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
+    engines: {node: '>=18'}
     hasBin: true
 
-  escalade@3.1.2:
-    resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
 
   escape-goat@2.1.1:
     resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==}
     engines: {node: '>=8'}
 
-  escape-goat@4.0.0:
-    resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==}
-    engines: {node: '>=12'}
-
   escape-html@1.0.3:
     resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
 
@@ -2688,10 +2791,6 @@ packages:
     resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
     engines: {node: '>=0.8.0'}
 
-  escape-string-regexp@2.0.0:
-    resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
-    engines: {node: '>=8'}
-
   escape-string-regexp@4.0.0:
     resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
     engines: {node: '>=10'}
@@ -2706,146 +2805,118 @@ packages:
     engines: {node: '>=6.0'}
     hasBin: true
 
-  eslint-compat-utils@0.5.0:
-    resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==}
+  eslint-compat-utils@0.5.1:
+    resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==}
     engines: {node: '>=12'}
     peerDependencies:
       eslint: '>=6.0.0'
 
-  eslint-config-love@47.0.0:
-    resolution: {integrity: sha512-wIeJhb4/NF7nE5Ltppg1e9dp1Auxx0+ZPRysrXQ3uBKlW4Nj/UiTZu4r3sKWCxo6HGcRcI4MC1Q5421y3fny2w==}
-    peerDependencies:
-      '@typescript-eslint/eslint-plugin': ^7.0.1
-      eslint: ^8.0.1
-      eslint-plugin-import: ^2.25.2
-      eslint-plugin-n: '^15.0.0 || ^16.0.0 '
-      eslint-plugin-promise: ^6.0.0
-      typescript: '*'
-
-  eslint-config-prettier@9.1.0:
-    resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
-    hasBin: true
-    peerDependencies:
-      eslint: '>=7.0.0'
-
-  eslint-config-standard@17.1.0:
-    resolution: {integrity: sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==}
-    engines: {node: '>=12.0.0'}
+  eslint-import-context@0.1.9:
+    resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==}
+    engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
     peerDependencies:
-      eslint: ^8.0.1
-      eslint-plugin-import: ^2.25.2
-      eslint-plugin-n: '^15.0.0 || ^16.0.0 '
-      eslint-plugin-promise: ^6.0.0
-
-  eslint-define-config@2.1.0:
-    resolution: {integrity: sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==}
-    engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'}
+      unrs-resolver: ^1.0.0
+    peerDependenciesMeta:
+      unrs-resolver:
+        optional: true
 
   eslint-import-resolver-node@0.3.9:
     resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
 
-  eslint-import-resolver-typescript@3.6.1:
-    resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
+  eslint-import-resolver-typescript@3.10.1:
+    resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       eslint: '*'
       eslint-plugin-import: '*'
-
-  eslint-module-utils@2.8.1:
-    resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
-    engines: {node: '>=4'}
-    peerDependencies:
-      '@typescript-eslint/parser': '*'
-      eslint: '*'
-      eslint-import-resolver-node: '*'
-      eslint-import-resolver-typescript: '*'
-      eslint-import-resolver-webpack: '*'
+      eslint-plugin-import-x: '*'
     peerDependenciesMeta:
-      '@typescript-eslint/parser':
-        optional: true
-      eslint:
-        optional: true
-      eslint-import-resolver-node:
-        optional: true
-      eslint-import-resolver-typescript:
+      eslint-plugin-import:
         optional: true
-      eslint-import-resolver-webpack:
+      eslint-plugin-import-x:
         optional: true
 
-  eslint-plugin-es-x@7.6.0:
-    resolution: {integrity: sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==}
+  eslint-plugin-es-x@7.8.0:
+    resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       eslint: '>=8'
 
-  eslint-plugin-import@2.29.1:
-    resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
-    engines: {node: '>=4'}
+  eslint-plugin-import-x@4.16.1:
+    resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      '@typescript-eslint/parser': '*'
-      eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+      '@typescript-eslint/utils': ^8.0.0
+      eslint: ^8.57.0 || ^9.0.0
+      eslint-import-resolver-node: '*'
     peerDependenciesMeta:
-      '@typescript-eslint/parser':
+      '@typescript-eslint/utils':
+        optional: true
+      eslint-import-resolver-node:
         optional: true
 
-  eslint-plugin-jsdoc@48.2.5:
-    resolution: {integrity: sha512-ZeTfKV474W1N9niWfawpwsXGu+ZoMXu4417eBROX31d7ZuOk8zyG66SO77DpJ2+A9Wa2scw/jRqBPnnQo7VbcQ==}
-    engines: {node: '>=18'}
+  eslint-plugin-jsdoc@51.3.3:
+    resolution: {integrity: sha512-8XK/9wncTh4PPntQfM4iYJ2v/kvX4qsfBzp+dTnyxpERWhl2R9hEJw1ihws+yAecg9CC6ExTfMInEg3wSK9kWA==}
+    engines: {node: '>=20.11.0'}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
 
-  eslint-plugin-n@17.7.0:
-    resolution: {integrity: sha512-4Jg4ZKVE4VjHig2caBqPHYNW5na84RVufUuipFLJbgM/G57O6FdpUKJbHakCDJb/yjQuyqVzYWRtU3HNYaZUwg==}
+  eslint-plugin-n@17.21.0:
+    resolution: {integrity: sha512-1+iZ8We4ZlwVMtb/DcHG3y5/bZOdazIpa/4TySo22MLKdwrLcfrX0hbadnCvykSQCCmkAnWmIP8jZVb2AAq29A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: '>=8.23.0'
 
-  eslint-plugin-prettier@5.1.3:
-    resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
-    engines: {node: ^14.18.0 || >=16.0.0}
+  eslint-plugin-perfectionist@4.15.0:
+    resolution: {integrity: sha512-pC7PgoXyDnEXe14xvRUhBII8A3zRgggKqJFx2a82fjrItDs1BSI7zdZnQtM2yQvcyod6/ujmzb7ejKPx8lZTnw==}
+    engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
-      '@types/eslint': '>=8.0.0'
-      eslint: '>=8.0.0'
-      eslint-config-prettier: '*'
-      prettier: '>=3.0.0'
-    peerDependenciesMeta:
-      '@types/eslint':
-        optional: true
-      eslint-config-prettier:
-        optional: true
+      eslint: '>=8.45.0'
 
-  eslint-plugin-promise@6.1.1:
-    resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  eslint-plugin-promise@7.2.1:
+    resolution: {integrity: sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^7.0.0 || ^8.0.0
+      eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
 
-  eslint-plugin-simple-import-sort@12.1.0:
-    resolution: {integrity: sha512-Y2fqAfC11TcG/WP3TrI1Gi3p3nc8XJyEOJYHyEPEGI/UAgNx6akxxlX74p7SbAQdLcgASKhj8M0GKvH3vq/+ig==}
+  eslint-plugin-react@7.37.5:
+    resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+    engines: {node: '>=4'}
     peerDependencies:
-      eslint: '>=5.0.0'
+      eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
 
-  eslint-plugin-tsdoc@0.2.17:
-    resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==}
-
-  eslint-plugin-vue@9.26.0:
-    resolution: {integrity: sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==}
-    engines: {node: ^14.17.0 || >=16.0.0}
+  eslint-plugin-vue@10.3.0:
+    resolution: {integrity: sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+      '@typescript-eslint/parser': ^7.0.0 || ^8.0.0
+      eslint: ^8.57.0 || ^9.0.0
+      vue-eslint-parser: ^10.0.0
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
 
-  eslint-scope@7.2.2:
-    resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  eslint-scope@8.4.0:
+    resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   eslint-visitor-keys@3.4.3:
     resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
-  eslint@8.57.0:
-    resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  eslint-visitor-keys@4.2.1:
+    resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint@9.30.1:
+    resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
+    peerDependencies:
+      jiti: '*'
+    peerDependenciesMeta:
+      jiti:
+        optional: true
 
   esm@3.2.25:
     resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
@@ -2855,17 +2926,17 @@ packages:
     resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
     engines: {node: '>=0.10'}
 
-  espree@9.6.1:
-    resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  espree@10.4.0:
+    resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   esprima@4.0.1:
     resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
     engines: {node: '>=4'}
     hasBin: true
 
-  esquery@1.5.0:
-    resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+  esquery@1.6.0:
+    resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
     engines: {node: '>=0.10'}
 
   esrecurse@4.3.0:
@@ -2921,14 +2992,6 @@ packages:
     resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
     engines: {node: '>=10'}
 
-  execa@5.1.1:
-    resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
-    engines: {node: '>=10'}
-
-  execa@8.0.1:
-    resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
-    engines: {node: '>=16.17'}
-
   execspawn@1.0.1:
     resolution: {integrity: sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg==}
 
@@ -2936,9 +2999,9 @@ packages:
     resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
     engines: {node: '>=6'}
 
-  expect@29.7.0:
-    resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  expect-type@1.2.1:
+    resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
+    engines: {node: '>=12.0.0'}
 
   ext@1.7.0:
     resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
@@ -2957,11 +3020,12 @@ packages:
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
-  fast-diff@1.3.0:
-    resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+  fast-equals@5.2.2:
+    resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
+    engines: {node: '>=6.0.0'}
 
-  fast-glob@3.3.2:
-    resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+  fast-glob@3.3.3:
+    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
     engines: {node: '>=8.6.0'}
 
   fast-json-stable-stringify@2.1.0:
@@ -2973,18 +3037,25 @@ packages:
   fast-safe-stringify@2.1.1:
     resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
 
-  fastq@1.17.1:
-    resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+  fast-uri@3.0.6:
+    resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
+
+  fastq@1.19.1:
+    resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+  fdir@6.4.6:
+    resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
 
   fecha@4.2.3:
     resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
 
-  fetch-blob@3.2.0:
-    resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
-    engines: {node: ^12.20 || >= 14.13}
-
-  figlet@1.7.0:
-    resolution: {integrity: sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==}
+  figlet@1.8.1:
+    resolution: {integrity: sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg==}
     engines: {node: '>= 0.4.0'}
     hasBin: true
 
@@ -2992,9 +3063,9 @@ packages:
     resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
     engines: {node: '>=4'}
 
-  file-entry-cache@6.0.1:
-    resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
-    engines: {node: ^10.12.0 || >=12.0.0}
+  file-entry-cache@8.0.0:
+    resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+    engines: {node: '>=16.0.0'}
 
   file-stream-rotator@0.6.1:
     resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==}
@@ -3006,8 +3077,8 @@ packages:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
 
-  finalhandler@1.2.0:
-    resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+  finalhandler@2.1.0:
+    resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
     engines: {node: '>= 0.8'}
 
   find-up@3.0.0:
@@ -3025,24 +3096,25 @@ packages:
   flame-gradient@1.0.0:
     resolution: {integrity: sha512-9ejk16/DqvQJ4dHsh68W/4N0zmVQ60zukyUuEHrTbf5pJvP4JqlIdke86Z9174PZokRCXAntY5+H1txSyC7mUA==}
 
-  flat-cache@3.2.0:
-    resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
-    engines: {node: ^10.12.0 || >=12.0.0}
+  flat-cache@4.0.1:
+    resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+    engines: {node: '>=16'}
 
   flatstr@1.0.12:
     resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==}
 
-  flatted@3.3.1:
-    resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
+  flatted@3.3.3:
+    resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
 
   fn.name@1.1.0:
     resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
 
-  for-each@0.3.3:
-    resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+  for-each@0.3.5:
+    resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+    engines: {node: '>= 0.4'}
 
-  foreground-child@3.1.1:
-    resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
+  foreground-child@3.3.1:
+    resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
     engines: {node: '>=14'}
 
   forever-agent@0.6.1:
@@ -3056,17 +3128,13 @@ packages:
     resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
     engines: {node: '>= 0.12'}
 
-  form-data@4.0.0:
-    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+  form-data@4.0.3:
+    resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
     engines: {node: '>= 6'}
 
-  formdata-polyfill@4.0.10:
-    resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
-    engines: {node: '>=12.20.0'}
-
-  fresh@0.5.2:
-    resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
-    engines: {node: '>= 0.6'}
+  fresh@2.0.0:
+    resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+    engines: {node: '>= 0.8'}
 
   from2-string@1.1.0:
     resolution: {integrity: sha512-m8vCh+KnXXXBtfF2VUbiYlQ+nczLcntB0BrtNgpmLkHylhObe9WF1b2LZjBBzrZzA6P4mkEla6ZYQoOUTG8cYA==}
@@ -3081,8 +3149,8 @@ packages:
     resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
     engines: {node: '>=12'}
 
-  fs-extra@11.2.0:
-    resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
+  fs-extra@11.3.0:
+    resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
     engines: {node: '>=14.14'}
 
   fs-minipass@2.1.0:
@@ -3100,8 +3168,8 @@ packages:
   function-bind@1.1.2:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
 
-  function.prototype.name@1.1.6:
-    resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+  function.prototype.name@1.1.8:
+    resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
     engines: {node: '>= 0.4'}
 
   functions-have-names@1.2.3:
@@ -3118,6 +3186,10 @@ packages:
   generate-object-property@1.2.0:
     resolution: {integrity: sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==}
 
+  gensequence@7.0.0:
+    resolution: {integrity: sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==}
+    engines: {node: '>=18'}
+
   gensync@1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
@@ -3129,21 +3201,22 @@ packages:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
-  get-east-asian-width@1.2.0:
-    resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
+  get-east-asian-width@1.3.0:
+    resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
     engines: {node: '>=18'}
 
-  get-func-name@2.0.2:
-    resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
-
-  get-intrinsic@1.2.4:
-    resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+  get-intrinsic@1.3.0:
+    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
     engines: {node: '>= 0.4'}
 
   get-package-type@0.1.0:
     resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
     engines: {node: '>=8.0.0'}
 
+  get-proto@1.0.1:
+    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+    engines: {node: '>= 0.4'}
+
   get-stream@5.2.0:
     resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
     engines: {node: '>=8'}
@@ -3152,20 +3225,12 @@ packages:
     resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
     engines: {node: '>=10'}
 
-  get-stream@8.0.1:
-    resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
-    engines: {node: '>=16'}
-
-  get-symbol-description@1.0.2:
-    resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
+  get-symbol-description@1.1.0:
+    resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
     engines: {node: '>= 0.4'}
 
-  get-tsconfig@4.7.5:
-    resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==}
-
-  get-uri@6.0.3:
-    resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==}
-    engines: {node: '>= 14'}
+  get-tsconfig@4.10.1:
+    resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
 
   getopts@2.3.0:
     resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==}
@@ -3178,12 +3243,6 @@ packages:
     engines: {node: '>=16'}
     hasBin: true
 
-  git-up@7.0.0:
-    resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==}
-
-  git-url-parse@14.0.0:
-    resolution: {integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==}
-
   github-from-package@0.0.0:
     resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
 
@@ -3195,13 +3254,18 @@ packages:
     resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
     engines: {node: '>=10.13.0'}
 
-  glob@10.3.15:
-    resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==}
-    engines: {node: '>=16 || 14 >=14.18'}
+  glob@10.4.5:
+    resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+    hasBin: true
+
+  glob@11.0.3:
+    resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
+    engines: {node: 20 || >=22}
     hasBin: true
 
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
 
   global-directory@4.0.1:
     resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
@@ -3211,16 +3275,12 @@ packages:
     resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
     engines: {node: '>=10'}
 
-  globals@11.12.0:
-    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
-    engines: {node: '>=4'}
-
-  globals@13.24.0:
-    resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
-    engines: {node: '>=8'}
+  globals@14.0.0:
+    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+    engines: {node: '>=18'}
 
-  globals@15.3.0:
-    resolution: {integrity: sha512-cCdyVjIUVTtX8ZsPkq1oCsOsLmGIswqnjZYMJJTGaNApj1yHtLSymKhwH51ttirREn75z3p4k051clwg7rvNKA==}
+  globals@15.15.0:
+    resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
     engines: {node: '>=18'}
 
   globalthis@1.0.4:
@@ -3231,31 +3291,20 @@ packages:
     resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
     engines: {node: '>=10'}
 
-  globby@14.0.1:
-    resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==}
-    engines: {node: '>=18'}
-
-  gopd@1.0.1:
-    resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+  gopd@1.2.0:
+    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+    engines: {node: '>= 0.4'}
 
   got@12.6.1:
     resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
     engines: {node: '>=14.16'}
 
-  graceful-fs@4.2.10:
-    resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
-
   graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
   graphemer@1.4.0:
     resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
 
-  handlebars@4.7.8:
-    resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
-    engines: {node: '>=0.4.7'}
-    hasBin: true
-
   har-schema@2.0.0:
     resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
     engines: {node: '>=4'}
@@ -3272,8 +3321,9 @@ packages:
   has-async-hooks@1.0.0:
     resolution: {integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==}
 
-  has-bigints@1.0.2:
-    resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+  has-bigints@1.1.0:
+    resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+    engines: {node: '>= 0.4'}
 
   has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
@@ -3283,15 +3333,19 @@ packages:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
 
+  has-own-prop@2.0.0:
+    resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==}
+    engines: {node: '>=8'}
+
   has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
 
-  has-proto@1.0.3:
-    resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+  has-proto@1.2.0:
+    resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
     engines: {node: '>= 0.4'}
 
-  has-symbols@1.0.3:
-    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+  has-symbols@1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
     engines: {node: '>= 0.4'}
 
   has-tostringtag@1.0.2:
@@ -3309,13 +3363,12 @@ packages:
     resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==}
     engines: {node: '>= 0.4.0'}
 
-  hash-base@3.0.4:
-    resolution: {integrity: sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==}
-    engines: {node: '>=4'}
+  hash-base@2.0.2:
+    resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==}
 
-  hash-base@3.1.0:
-    resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==}
-    engines: {node: '>=4'}
+  hash-base@3.0.5:
+    resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==}
+    engines: {node: '>= 0.10'}
 
   hash.js@1.1.7:
     resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}
@@ -3324,8 +3377,8 @@ packages:
     resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
     engines: {node: '>= 0.4'}
 
-  hdr-histogram-js@3.0.0:
-    resolution: {integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==}
+  hdr-histogram-js@3.0.1:
+    resolution: {integrity: sha512-l3GSdZL1Jr1C0kyb461tUjEdrRPZr8Qry7jByltf5JGrA0xvqOSrxRBfcrJqqV/AMEtqqhHhC6w8HW0gn76tRQ==}
     engines: {node: '>=14'}
 
   hdr-histogram-percentiles-obj@3.0.0:
@@ -3349,23 +3402,19 @@ packages:
   html-escaper@2.0.2:
     resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
 
-  html-tags@3.3.1:
-    resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
-    engines: {node: '>=8'}
-
   htmlescape@1.1.1:
     resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
     engines: {node: '>=0.10'}
 
-  http-cache-semantics@4.1.1:
-    resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
+  http-cache-semantics@4.2.0:
+    resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
 
   http-errors@2.0.0:
     resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
     engines: {node: '>= 0.8'}
 
-  http-parser-js@0.5.8:
-    resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
+  http-parser-js@0.5.10:
+    resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==}
 
   http-proxy-agent@4.0.1:
     resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
@@ -3393,32 +3442,24 @@ packages:
     resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
     engines: {node: '>= 6'}
 
-  https-proxy-agent@7.0.4:
-    resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
+  https-proxy-agent@7.0.6:
+    resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
     engines: {node: '>= 14'}
 
   human-signals@1.1.1:
     resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==}
     engines: {node: '>=8.12.0'}
 
-  human-signals@2.1.0:
-    resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
-    engines: {node: '>=10.17.0'}
-
-  human-signals@5.0.0:
-    resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
-    engines: {node: '>=16.17.0'}
-
   humanize-ms@1.2.1:
     resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
 
-  husky@9.0.11:
-    resolution: {integrity: sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==}
+  husky@9.1.7:
+    resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
     engines: {node: '>=18'}
     hasBin: true
 
-  hyperid@3.2.0:
-    resolution: {integrity: sha512-PdTtDo+Rmza9nEhTunaDSUKwbC69TIzLEpZUwiB6f+0oqmY0UPfhyHCPt6K1NQ4WFv5yJBTG5vELztVWP+nEVQ==}
+  hyperid@3.3.0:
+    resolution: {integrity: sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==}
 
   hyperscript-attribute-to-property@1.0.2:
     resolution: {integrity: sha512-oerMul16jZCmrbNsUw8QgrtDzF8lKgFri1bKQjReLw1IhiiNkI59CWuzZjJDGT79UQ1YiWqXhJMv/tRMVqgtkA==}
@@ -3437,22 +3478,22 @@ packages:
   ieee754@1.2.1:
     resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
 
-  ignore@5.3.1:
-    resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  ignore@7.0.5:
+    resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
     engines: {node: '>= 4'}
 
-  import-fresh@3.3.0:
-    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+  import-fresh@3.3.1:
+    resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
     engines: {node: '>=6'}
 
   import-lazy@2.1.0:
     resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==}
     engines: {node: '>=4'}
 
-  import-lazy@4.0.0:
-    resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
-    engines: {node: '>=8'}
-
   import-meta-resolve@4.1.0:
     resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
 
@@ -3469,6 +3510,7 @@ packages:
 
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
 
   inherits@2.0.3:
     resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
@@ -3487,10 +3529,6 @@ packages:
     resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 
-  ini@4.1.2:
-    resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==}
-    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
-
   inline-source-map@0.6.3:
     resolution: {integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==}
 
@@ -3498,10 +3536,6 @@ packages:
     resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==}
     engines: {node: '>=6.0.0'}
 
-  inquirer@9.2.22:
-    resolution: {integrity: sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw==}
-    engines: {node: '>=18'}
-
   insert-module-globals@7.2.1:
     resolution: {integrity: sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==}
     hasBin: true
@@ -3510,17 +3544,13 @@ packages:
     resolution: {integrity: sha512-TBcZ0qC9dgdmcxL93OoqkY/RZXJtIi0i07phX/QyYk2ysmJtZex59dgTj4Doq50N9CG9dLRe/RIudc/5CCoFNw==}
     engines: {node: '>=12.20'}
 
-  internal-slot@1.0.7:
-    resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
+  internal-slot@1.1.0:
+    resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
     engines: {node: '>= 0.4'}
 
   internmap@1.0.1:
     resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
 
-  interpret@1.4.0:
-    resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
-    engines: {node: '>= 0.10'}
-
   interpret@2.2.0:
     resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
     engines: {node: '>= 0.10'}
@@ -3535,12 +3565,12 @@ packages:
   is-any-array@2.0.1:
     resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==}
 
-  is-arguments@1.1.1:
-    resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
+  is-arguments@1.2.0:
+    resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
     engines: {node: '>= 0.4'}
 
-  is-array-buffer@3.0.4:
-    resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
+  is-array-buffer@3.0.5:
+    resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
     engines: {node: '>= 0.4'}
 
   is-arrayish@0.2.1:
@@ -3549,8 +3579,13 @@ packages:
   is-arrayish@0.3.2:
     resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
 
-  is-bigint@1.0.4:
-    resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+  is-async-function@2.1.1:
+    resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+    engines: {node: '>= 0.4'}
+
+  is-bigint@1.1.0:
+    resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+    engines: {node: '>= 0.4'}
 
   is-binary-path@2.1.0:
     resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
@@ -3559,8 +3594,8 @@ packages:
   is-boolean-attribute@0.0.1:
     resolution: {integrity: sha512-0kXT52Scokg2Miscvsn5UVqg6y1691vcLJcagie1YHJB4zOEuAhMERLX992jtvaStGy2xQTqOtJhvmG/MK1T5w==}
 
-  is-boolean-object@1.1.2:
-    resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+  is-boolean-object@1.2.2:
+    resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
     engines: {node: '>= 0.4'}
 
   is-buffer@1.1.6:
@@ -3570,9 +3605,8 @@ packages:
     resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
     engines: {node: '>=4'}
 
-  is-builtin-module@3.2.1:
-    resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
-    engines: {node: '>=6'}
+  is-bun-module@2.0.0:
+    resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
 
   is-callable@1.2.7:
     resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
@@ -3582,19 +3616,16 @@ packages:
     resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==}
     hasBin: true
 
-  is-ci@3.0.1:
-    resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
-    hasBin: true
-
-  is-core-module@2.13.1:
-    resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+  is-core-module@2.16.1:
+    resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+    engines: {node: '>= 0.4'}
 
-  is-data-view@1.0.1:
-    resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==}
+  is-data-view@1.0.2:
+    resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
     engines: {node: '>= 0.4'}
 
-  is-date-object@1.0.5:
-    resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+  is-date-object@1.1.0:
+    resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
     engines: {node: '>= 0.4'}
 
   is-docker@2.2.1:
@@ -3602,15 +3633,14 @@ packages:
     engines: {node: '>=8'}
     hasBin: true
 
-  is-docker@3.0.0:
-    resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-    hasBin: true
-
   is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
 
+  is-finalizationregistry@1.1.1:
+    resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+    engines: {node: '>= 0.4'}
+
   is-fullwidth-code-point@1.0.0:
     resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
     engines: {node: '>=0.10.0'}
@@ -3631,24 +3661,14 @@ packages:
     resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
     engines: {node: '>=18'}
 
-  is-generator-function@1.0.10:
-    resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
+  is-generator-function@1.1.0:
+    resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
     engines: {node: '>= 0.4'}
 
   is-glob@4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
 
-  is-in-ci@0.1.0:
-    resolution: {integrity: sha512-d9PXLEY0v1iJ64xLiQMJ51J128EYHAaOR4yZqQi8aHGfw6KgifM3/Viw1oZZ1GCVmb3gBuyhLyHj0HgR2DhSXQ==}
-    engines: {node: '>=18'}
-    hasBin: true
-
-  is-inside-container@1.0.0:
-    resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
-    engines: {node: '>=14.16'}
-    hasBin: true
-
   is-installed-globally@0.4.0:
     resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
     engines: {node: '>=10'}
@@ -3657,10 +3677,6 @@ packages:
     resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
     engines: {node: '>=8'}
 
-  is-interactive@2.0.0:
-    resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
-    engines: {node: '>=12'}
-
   is-lambda@1.0.1:
     resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
 
@@ -3676,12 +3692,8 @@ packages:
     resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==}
     engines: {node: '>=10'}
 
-  is-npm@6.0.0:
-    resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
-  is-number-object@1.0.7:
-    resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+  is-number-object@1.1.1:
+    resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
     engines: {node: '>= 0.4'}
 
   is-number@7.0.0:
@@ -3706,43 +3718,36 @@ packages:
   is-property@1.0.2:
     resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
 
-  is-regex@1.1.4:
-    resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+  is-regex@1.2.1:
+    resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
     engines: {node: '>= 0.4'}
 
   is-set@2.0.3:
     resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
     engines: {node: '>= 0.4'}
 
-  is-shared-array-buffer@1.0.3:
-    resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
+  is-shared-array-buffer@1.0.4:
+    resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
     engines: {node: '>= 0.4'}
 
-  is-ssh@1.4.0:
-    resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==}
-
   is-stream@2.0.1:
     resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
     engines: {node: '>=8'}
 
-  is-stream@3.0.0:
-    resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
-  is-string@1.0.7:
-    resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+  is-string@1.1.1:
+    resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
     engines: {node: '>= 0.4'}
 
-  is-symbol@1.0.4:
-    resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+  is-symbol@1.1.1:
+    resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
     engines: {node: '>= 0.4'}
 
   is-text-path@2.0.0:
     resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==}
     engines: {node: '>=8'}
 
-  is-typed-array@1.1.13:
-    resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
+  is-typed-array@1.1.15:
+    resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
     engines: {node: '>= 0.4'}
 
   is-typedarray@1.0.0:
@@ -3752,16 +3757,17 @@ packages:
     resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
     engines: {node: '>=10'}
 
-  is-unicode-supported@1.3.0:
-    resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
-    engines: {node: '>=12'}
+  is-weakmap@2.0.2:
+    resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+    engines: {node: '>= 0.4'}
 
-  is-unicode-supported@2.0.0:
-    resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==}
-    engines: {node: '>=18'}
+  is-weakref@1.1.1:
+    resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+    engines: {node: '>= 0.4'}
 
-  is-weakref@1.0.2:
-    resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+  is-weakset@2.0.4:
+    resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+    engines: {node: '>= 0.4'}
 
   is-wsl@1.1.0:
     resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==}
@@ -3771,10 +3777,6 @@ packages:
     resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
     engines: {node: '>=8'}
 
-  is-wsl@3.1.0:
-    resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
-    engines: {node: '>=16'}
-
   is-yarn-global@0.3.0:
     resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==}
 
@@ -3790,10 +3792,6 @@ packages:
   isstream@0.1.2:
     resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
 
-  issue-parser@7.0.0:
-    resolution: {integrity: sha512-jgAw78HO3gs9UrKqJNQvfDj9Ouy8Mhu40fbEJ8yXff4MW8+/Fcn9iFjyWUQ6SKbX8ipPk3X5A3AyfYHRu6uVLw==}
-    engines: {node: ^18.17 || >=20.6.1}
-
   istanbul-lib-coverage@3.2.2:
     resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
     engines: {node: '>=8'}
@@ -3802,53 +3800,31 @@ packages:
     resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
     engines: {node: '>=10'}
 
-  istanbul-lib-source-maps@5.0.4:
-    resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==}
+  istanbul-lib-source-maps@5.0.6:
+    resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
     engines: {node: '>=10'}
 
   istanbul-reports@3.1.7:
     resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
     engines: {node: '>=8'}
 
-  iterate-iterator@1.0.2:
-    resolution: {integrity: sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==}
-
-  iterate-value@1.0.2:
-    resolution: {integrity: sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==}
-
-  jackspeak@2.3.6:
-    resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
-    engines: {node: '>=14'}
-
-  jest-diff@29.7.0:
-    resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
-  jest-get-type@29.6.3:
-    resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
-  jest-matcher-utils@29.7.0:
-    resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  iterator.prototype@1.1.5:
+    resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+    engines: {node: '>= 0.4'}
 
-  jest-message-util@29.7.0:
-    resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  jackspeak@3.4.3:
+    resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
 
-  jest-util@29.7.0:
-    resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  jackspeak@4.1.1:
+    resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
+    engines: {node: 20 || >=22}
 
-  jiti@1.21.0:
-    resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
+  jiti@2.4.2:
+    resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
     hasBin: true
 
-  jju@1.4.0:
-    resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
-
-  js-beautify@1.15.1:
-    resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
+  js-beautify@1.15.4:
+    resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
     engines: {node: '>=14'}
     hasBin: true
 
@@ -3859,8 +3835,8 @@ packages:
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
-  js-tokens@9.0.0:
-    resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==}
+  js-tokens@9.0.1:
+    resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
 
   js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
@@ -3872,22 +3848,22 @@ packages:
   jsbn@1.1.0:
     resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
 
-  jsdoc-type-pratt-parser@4.0.0:
-    resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==}
+  jsdoc-type-pratt-parser@4.1.0:
+    resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
     engines: {node: '>=12.0.0'}
 
-  jsdom@24.0.0:
-    resolution: {integrity: sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==}
+  jsdom@26.1.0:
+    resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
     engines: {node: '>=18'}
     peerDependencies:
-      canvas: ^2.11.2
+      canvas: ^3.0.0
     peerDependenciesMeta:
       canvas:
         optional: true
 
-  jsesc@2.5.2:
-    resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
-    engines: {node: '>=4'}
+  jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
     hasBin: true
 
   json-buffer@3.0.1:
@@ -3914,10 +3890,6 @@ packages:
   json-stringify-safe@5.0.1:
     resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
 
-  json5@1.0.2:
-    resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
-    hasBin: true
-
   json5@2.2.3:
     resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
     engines: {node: '>=6'}
@@ -3939,6 +3911,10 @@ packages:
     resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
     engines: {node: '>=0.6.0'}
 
+  jsx-ast-utils@3.3.5:
+    resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+    engines: {node: '>=4.0'}
+
   keyv@4.5.4:
     resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
 
@@ -3980,10 +3956,6 @@ packages:
     resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==}
     engines: {node: '>=8'}
 
-  latest-version@7.0.0:
-    resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==}
-    engines: {node: '>=14.16'}
-
   leven@2.1.0:
     resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==}
     engines: {node: '>=0.10.0'}
@@ -3996,26 +3968,22 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
-  lilconfig@3.0.0:
-    resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
+  lilconfig@3.1.3:
+    resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
     engines: {node: '>=14'}
 
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
-  lint-staged@15.2.2:
-    resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==}
-    engines: {node: '>=18.12.0'}
+  lint-staged@16.1.2:
+    resolution: {integrity: sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==}
+    engines: {node: '>=20.17'}
     hasBin: true
 
-  listr2@8.0.1:
-    resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==}
+  listr2@8.3.3:
+    resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
     engines: {node: '>=18.0.0'}
 
-  local-pkg@0.5.0:
-    resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
-    engines: {node: '>=14'}
-
   locate-path@3.0.0:
     resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
     engines: {node: '>=6'}
@@ -4028,15 +3996,9 @@ packages:
     resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
-  lodash-es@4.17.21:
-    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
-
   lodash.camelcase@4.3.0:
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
 
-  lodash.capitalize@4.2.1:
-    resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==}
-
   lodash.chunk@4.2.0:
     resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==}
 
@@ -4046,18 +4008,12 @@ packages:
   lodash.debounce@4.0.8:
     resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
 
-  lodash.escaperegexp@4.1.2:
-    resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
-
   lodash.flatten@4.4.0:
     resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
 
   lodash.isplainobject@4.0.6:
     resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
 
-  lodash.isstring@4.0.1:
-    resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
-
   lodash.kebabcase@4.1.1:
     resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
 
@@ -4079,9 +4035,6 @@ packages:
   lodash.uniq@4.5.0:
     resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
 
-  lodash.uniqby@4.7.0:
-    resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==}
-
   lodash.upperfirst@4.3.1:
     resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==}
 
@@ -4092,16 +4045,12 @@ packages:
     resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
     engines: {node: '>=10'}
 
-  log-symbols@6.0.0:
-    resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
+  log-update@6.1.0:
+    resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
     engines: {node: '>=18'}
 
-  log-update@6.0.0:
-    resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==}
-    engines: {node: '>=18'}
-
-  logform@2.6.0:
-    resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
+  logform@2.7.0:
+    resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
     engines: {node: '>= 12.0.0'}
 
   long@4.0.0:
@@ -4111,8 +4060,8 @@ packages:
     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
     hasBin: true
 
-  loupe@2.3.7:
-    resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+  loupe@3.1.4:
+    resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
 
   lower-case@1.1.4:
     resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
@@ -4121,9 +4070,12 @@ packages:
     resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
-  lru-cache@10.2.2:
-    resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
-    engines: {node: 14 || >=16.14}
+  lru-cache@10.4.3:
+    resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+  lru-cache@11.1.0:
+    resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
+    engines: {node: 20 || >=22}
 
   lru-cache@5.1.1:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -4132,29 +4084,21 @@ packages:
     resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
     engines: {node: '>=10'}
 
-  lru-cache@7.18.3:
-    resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
-    engines: {node: '>=12'}
-
   macos-release@2.5.1:
     resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==}
     engines: {node: '>=6'}
 
-  macos-release@3.2.0:
-    resolution: {integrity: sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   magic-string@0.23.2:
     resolution: {integrity: sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==}
 
   magic-string@0.25.1:
     resolution: {integrity: sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==}
 
-  magic-string@0.30.10:
-    resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
+  magic-string@0.30.17:
+    resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
 
-  magicast@0.3.4:
-    resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
+  magicast@0.3.5:
+    resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
 
   make-dir@3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
@@ -4174,10 +4118,14 @@ packages:
   manage-path@2.0.0:
     resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==}
 
-  mariadb@3.3.0:
-    resolution: {integrity: sha512-sAL4bJgbfCAtXcE8bXI+NAMzVaPNkIU8hRZUXYfgNFoWB9U57G3XQiMeCx/A6IrS6y7kGwBLylrwgsZQ8kUYlw==}
+  mariadb@3.4.2:
+    resolution: {integrity: sha512-B17vhYRHDMQ1XXvhSWsvKJbpw3Q8B6py93ThBEXZXSgxIbqnKqoHK1RzoPLbIxoEzWN3jA86ZaMMc3IG6L5wsw==}
     engines: {node: '>= 14'}
 
+  math-intrinsics@1.1.0:
+    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+    engines: {node: '>= 0.4'}
+
   md5.js@1.3.5:
     resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
 
@@ -4198,12 +4146,12 @@ packages:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
 
-  micromatch@4.0.5:
-    resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
 
-  mikro-orm@6.2.7:
-    resolution: {integrity: sha512-1NHDdwcy4X7Jfu3KLXKXClAbPq8/BTgsXblKRUt1tTA0dvJSl8k4LCu9sCx/ren1wDq8B1VkoE7EFG+M6YRmhQ==}
+  mikro-orm@6.4.16:
+    resolution: {integrity: sha512-a+19cRuEPEJ3qf5Vpv4HO9TAxo/I6/rP1bZT93ln8YtNzAbCrcLC766EG6upLS6Sf7OLqdq0cXs5mUN9ESQQrg==}
     engines: {node: '>= 18.12.0'}
 
   miller-rabin@4.0.1:
@@ -4214,14 +4162,17 @@ packages:
     resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
     engines: {node: '>= 0.6'}
 
+  mime-db@1.54.0:
+    resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+    engines: {node: '>= 0.6'}
+
   mime-types@2.1.35:
     resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
     engines: {node: '>= 0.6'}
 
-  mime@1.6.0:
-    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
-    engines: {node: '>=4'}
-    hasBin: true
+  mime-types@3.0.1:
+    resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
+    engines: {node: '>= 0.6'}
 
   mimic-fn@1.2.0:
     resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==}
@@ -4235,9 +4186,9 @@ packages:
     resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
     engines: {node: '>=8'}
 
-  mimic-fn@4.0.0:
-    resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
-    engines: {node: '>=12'}
+  mimic-function@5.0.1:
+    resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+    engines: {node: '>=18'}
 
   mimic-response@3.1.0:
     resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
@@ -4257,6 +4208,10 @@ packages:
   minimalistic-crypto-utils@1.0.1:
     resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
 
+  minimatch@10.0.3:
+    resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
+    engines: {node: 20 || >=22}
+
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
@@ -4264,8 +4219,8 @@ packages:
     resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
     engines: {node: '>=16 || 14 >=14.17'}
 
-  minimatch@9.0.4:
-    resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
+  minimatch@9.0.5:
+    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
     engines: {node: '>=16 || 14 >=14.17'}
 
   minimist@1.2.8:
@@ -4299,16 +4254,16 @@ packages:
     resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
     engines: {node: '>=8'}
 
-  minipass@7.1.1:
-    resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==}
+  minipass@7.1.2:
+    resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
     engines: {node: '>=16 || 14 >=14.17'}
 
   minizlib@2.1.2:
     resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
     engines: {node: '>= 8'}
 
-  minizlib@3.0.1:
-    resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
+  minizlib@3.0.2:
+    resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
     engines: {node: '>= 18'}
 
   mkdirp-classic@0.5.3:
@@ -4351,11 +4306,8 @@ packages:
   ml-xsadd@2.0.0:
     resolution: {integrity: sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA==}
 
-  mlly@1.7.0:
-    resolution: {integrity: sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==}
-
-  mnemonist@0.40.0-rc1:
-    resolution: {integrity: sha512-38L0xGDezsPweee5i7duiaCRzlkkCJorozW6Rta60iel7ZkT4vF6jDIfr101NxE3rsAsumuOvc8yiTGZD5gG4w==}
+  mnemonist@0.40.3:
+    resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==}
 
   module-deps@6.2.3:
     resolution: {integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==}
@@ -4365,15 +4317,15 @@ packages:
   moment@2.30.1:
     resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
 
-  mongodb-connection-string-url@3.0.1:
-    resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==}
+  mongodb-connection-string-url@3.0.2:
+    resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==}
 
-  mongodb@6.6.2:
-    resolution: {integrity: sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==}
+  mongodb@6.17.0:
+    resolution: {integrity: sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==}
     engines: {node: '>=16.20.1'}
     peerDependencies:
       '@aws-sdk/credential-providers': ^3.188.0
-      '@mongodb-js/zstd': ^1.1.0
+      '@mongodb-js/zstd': ^1.1.0 || ^2.0.0
       gcp-metadata: ^5.2.0
       kerberos: ^2.0.1
       mongodb-client-encryption: '>=6.0.0 <7'
@@ -4395,11 +4347,8 @@ packages:
       socks:
         optional: true
 
-  morphdom@2.7.2:
-    resolution: {integrity: sha512-Dqb/lHFyTi7SZpY0a5R4I/0Edo+iPMbaUexsHHsLAByyixCDiLHPHyVoKVmrpL0THcT7V9Cgev9y21TQYq6wQg==}
-
-  ms@2.0.0:
-    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+  morphdom@2.7.5:
+    resolution: {integrity: sha512-z6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==}
 
   ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@@ -4413,13 +4362,13 @@ packages:
   mute-stream@0.0.7:
     resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==}
 
-  mute-stream@1.0.0:
-    resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
-    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
-
   mutexify@1.4.0:
     resolution: {integrity: sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==}
 
+  nano-spawn@1.0.2:
+    resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==}
+    engines: {node: '>=20.17'}
+
   nanoassert@1.1.0:
     resolution: {integrity: sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ==}
 
@@ -4430,17 +4379,26 @@ packages:
   nanohtml@1.10.0:
     resolution: {integrity: sha512-r/3AQl+jxAxUIJRiKExUjBtFcE1cm4yTOsTIdVqqlxPNtBxJh522ANrcQYzdNHhPzbPgb7j6qujq6eGehBX0kg==}
 
-  nanoid@3.3.7:
-    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
-  napi-build-utils@1.0.2:
-    resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
+  napi-build-utils@2.0.0:
+    resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
+
+  napi-postinstall@0.3.0:
+    resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==}
+    engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+    hasBin: true
 
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
+  natural-orderby@5.0.0:
+    resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
+    engines: {node: '>=18'}
+
   ndarray-blas-level1@1.1.3:
     resolution: {integrity: sha512-g0Qzf+W0J2S/w1GeYlGuFjGRGGE+f+u8x4O8lhBtsrCaf++n/+YLTPKk1tovYmciL3zUePmwi/szoP5oj8zQvg==}
 
@@ -4468,20 +4426,16 @@ packages:
   ndarray@1.0.19:
     resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==}
 
-  negotiator@0.6.3:
-    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+  negotiator@0.6.4:
+    resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
     engines: {node: '>= 0.6'}
 
-  neo-async@2.6.2:
-    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
-
-  netmask@2.0.2:
-    resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
-    engines: {node: '>= 0.4.0'}
-
-  new-github-release-url@2.0.0:
-    resolution: {integrity: sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  neostandard@0.12.2:
+    resolution: {integrity: sha512-VZU8EZpSaNadp3rKEwBhVD1Kw8jE3AftQLkCyOaM7bWemL1LwsYRsBnAmXy2LjG9zO8t66qJdqB7ccwwORyrAg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    hasBin: true
+    peerDependencies:
+      eslint: ^9.0.0
 
   next-tick@1.1.0:
     resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
@@ -4489,17 +4443,12 @@ packages:
   no-case@2.3.2:
     resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
 
-  node-abi@3.62.0:
-    resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
+  node-abi@3.75.0:
+    resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==}
     engines: {node: '>=10'}
 
-  node-addon-api@7.1.0:
-    resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
-    engines: {node: ^16 || ^18 || >= 20}
-
-  node-domexception@1.0.0:
-    resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
-    engines: {node: '>=10.5.0'}
+  node-addon-api@7.1.1:
+    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
 
   node-fetch@2.6.13:
     resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==}
@@ -4510,21 +4459,8 @@ packages:
       encoding:
         optional: true
 
-  node-fetch@2.7.0:
-    resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
-    engines: {node: 4.x || >=6.0.0}
-    peerDependencies:
-      encoding: ^0.1.0
-    peerDependenciesMeta:
-      encoding:
-        optional: true
-
-  node-fetch@3.3.2:
-    resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
-  node-gyp-build@4.8.1:
-    resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
+  node-gyp-build@4.8.4:
+    resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
     hasBin: true
 
   node-gyp@8.4.1:
@@ -4532,8 +4468,8 @@ packages:
     engines: {node: '>= 10.12.0'}
     hasBin: true
 
-  node-releases@2.0.14:
-    resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+  node-releases@2.0.19:
+    resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
 
   nopt@5.0.0:
     resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
@@ -4553,18 +4489,14 @@ packages:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
 
-  normalize-url@8.0.1:
-    resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
+  normalize-url@8.0.2:
+    resolution: {integrity: sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==}
     engines: {node: '>=14.16'}
 
   npm-run-path@4.0.1:
     resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
     engines: {node: '>=8'}
 
-  npm-run-path@5.3.0:
-    resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   npmlog@6.0.2:
     resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -4577,41 +4509,46 @@ packages:
     resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
     engines: {node: '>=0.10.0'}
 
-  nwsapi@2.2.10:
-    resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==}
+  nwsapi@2.2.20:
+    resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
 
   oauth-sign@0.9.0:
     resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
 
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
   object-hash@3.0.0:
     resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
     engines: {node: '>= 6'}
 
-  object-inspect@1.13.1:
-    resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
+  object-inspect@1.13.4:
+    resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+    engines: {node: '>= 0.4'}
 
   object-keys@1.1.1:
     resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
     engines: {node: '>= 0.4'}
 
-  object.assign@4.1.5:
-    resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+  object.assign@4.1.7:
+    resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
     engines: {node: '>= 0.4'}
 
-  object.fromentries@2.0.8:
-    resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+  object.entries@1.1.9:
+    resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
     engines: {node: '>= 0.4'}
 
-  object.groupby@1.0.3:
-    resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+  object.fromentries@2.0.8:
+    resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
     engines: {node: '>= 0.4'}
 
-  object.values@1.2.0:
-    resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
+  object.values@1.2.1:
+    resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
     engines: {node: '>= 0.4'}
 
-  obliterator@2.0.4:
-    resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==}
+  obliterator@2.0.5:
+    resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
 
   on-finished@2.4.1:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
@@ -4635,12 +4572,8 @@ packages:
     resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
     engines: {node: '>=6'}
 
-  onetime@6.0.0:
-    resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
-    engines: {node: '>=12'}
-
-  open@10.1.0:
-    resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
+  onetime@7.0.0:
+    resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
     engines: {node: '>=18'}
 
   open@7.4.2:
@@ -4663,10 +4596,6 @@ packages:
     resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
     engines: {node: '>=10'}
 
-  ora@8.0.1:
-    resolution: {integrity: sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==}
-    engines: {node: '>=18'}
-
   os-browserify@0.3.0:
     resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==}
 
@@ -4674,14 +4603,14 @@ packages:
     resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==}
     engines: {node: '>=10'}
 
-  os-name@5.1.0:
-    resolution: {integrity: sha512-YEIoAnM6zFmzw3PQ201gCVCIWbXNyKObGlVvpAVvraAeOHnlYVKFssbA/riRX5R40WA6kKrZ7Dr7dWzO3nKSeQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   os-tmpdir@1.0.2:
     resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
     engines: {node: '>=0.10.0'}
 
+  own-keys@1.0.1:
+    resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+    engines: {node: '>= 0.4'}
+
   p-cancelable@3.0.0:
     resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
     engines: {node: '>=12.20'}
@@ -4698,10 +4627,6 @@ packages:
     resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
-  p-limit@5.0.0:
-    resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
-    engines: {node: '>=18'}
-
   p-locate@3.0.0:
     resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
     engines: {node: '>=6'}
@@ -4722,22 +4647,13 @@ packages:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
 
-  pac-proxy-agent@7.0.1:
-    resolution: {integrity: sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==}
-    engines: {node: '>= 14'}
-
-  pac-resolver@7.0.1:
-    resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
-    engines: {node: '>= 14'}
+  package-json-from-dist@1.0.1:
+    resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
 
   package-json@6.5.0:
     resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==}
     engines: {node: '>=8'}
 
-  package-json@8.1.1:
-    resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==}
-    engines: {node: '>=14.16'}
-
   pako@1.0.11:
     resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
 
@@ -4745,6 +4661,10 @@ packages:
     resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
     engines: {node: '>=6'}
 
+  parent-module@2.0.0:
+    resolution: {integrity: sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==}
+    engines: {node: '>=8'}
+
   parent-require@1.0.0:
     resolution: {integrity: sha512-2MXDNZC4aXdkkap+rBBMv0lUsfJqvX5/2FiYYnfCnorZt3Pk06/IOR5KeaoghgS2w07MLWgjbsnyaq6PdHn2LQ==}
     engines: {node: '>= 0.4.0'}
@@ -4756,23 +4676,18 @@ packages:
     resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==}
     engines: {node: '>= 0.10'}
 
-  parse-github-url@1.0.2:
-    resolution: {integrity: sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==}
-    engines: {node: '>=0.10.0'}
-    hasBin: true
+  parse-imports-exports@0.2.4:
+    resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
 
   parse-json@5.2.0:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
 
-  parse-path@7.0.0:
-    resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==}
+  parse-statements@1.0.11:
+    resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
 
-  parse-url@8.1.0:
-    resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==}
-
-  parse5@7.1.2:
-    resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
+  parse5@7.3.0:
+    resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
 
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
@@ -4801,10 +4716,6 @@ packages:
     resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
     engines: {node: '>=8'}
 
-  path-key@4.0.0:
-    resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
-    engines: {node: '>=12'}
-
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
@@ -4816,37 +4727,46 @@ packages:
     resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
     engines: {node: '>=16 || 14 >=14.18'}
 
+  path-scurry@2.0.0:
+    resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
+    engines: {node: 20 || >=22}
+
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
 
-  path-type@5.0.0:
-    resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==}
-    engines: {node: '>=12'}
-
-  pathe@1.1.2:
-    resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+  pathe@2.0.3:
+    resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
 
-  pathval@1.1.1:
-    resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+  pathval@2.0.1:
+    resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+    engines: {node: '>= 14.16'}
 
-  pbkdf2@3.1.2:
-    resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
+  pbkdf2@3.1.3:
+    resolution: {integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==}
     engines: {node: '>=0.12'}
 
+  peowly@1.3.2:
+    resolution: {integrity: sha512-BYIrwr8JCXY49jUZscgw311w9oGEKo7ux/s+BxrhKTQbiQ0iYNdZNJ5LgagaeercQdFHwnR7Z5IxxFWVQ+BasQ==}
+    engines: {node: '>=18.6.0'}
+
   performance-now@2.1.0:
     resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
 
   pg-connection-string@2.6.2:
     resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
 
-  picocolors@1.0.1:
-    resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 
   picomatch@2.3.1:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
+  picomatch@4.0.2:
+    resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+    engines: {node: '>=12'}
+
   pidtree@0.6.0:
     resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
     engines: {node: '>=0.10'}
@@ -4856,19 +4776,16 @@ packages:
     resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
     engines: {node: '>=0.10.0'}
 
-  pkg-types@1.1.1:
-    resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==}
-
   pkg-up@3.1.0:
     resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
     engines: {node: '>=8'}
 
-  poolifier@4.0.10:
-    resolution: {integrity: sha512-PSitQC7KbzqnPrHLOLl0nebqDbGtEPN1fNVZgAdYPK7jmswAiDaW9z4VDr1fW45mkPio654bEg77QvsScUG+WQ==}
-    engines: {node: '>=18.0.0', pnpm: '>=9.0.0'}
+  poolifier@5.0.2:
+    resolution: {integrity: sha512-Y+81llBJ/c0ky5ndwZGoVyF+QX+fTVbDXXcHDpVArGTjQbnz4jCfQwRjPhxdREydSu7QAR7GeKLQmLhhMUpVug==}
+    engines: {node: '>=20.11.0', pnpm: '>=9.0.0'}
 
-  possible-typed-array-names@1.0.0:
-    resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
+  possible-typed-array-names@1.1.0:
+    resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
     engines: {node: '>= 0.4'}
 
   postcss-import@13.0.0:
@@ -4877,19 +4794,19 @@ packages:
     peerDependencies:
       postcss: ^8.0.0
 
-  postcss-selector-parser@6.0.16:
-    resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
+  postcss-selector-parser@6.1.2:
+    resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
     engines: {node: '>=4'}
 
   postcss-value-parser@4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
 
-  postcss@8.4.38:
-    resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
+  postcss@8.5.6:
+    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
     engines: {node: ^10 || ^12 || >=14}
 
-  prebuild-install@7.1.2:
-    resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==}
+  prebuild-install@7.1.3:
+    resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -4901,12 +4818,8 @@ packages:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
 
-  prettier-linter-helpers@1.0.0:
-    resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
-    engines: {node: '>=6.0.0'}
-
-  prettier@3.2.5:
-    resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+  prettier@3.6.2:
+    resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
     engines: {node: '>=14'}
     hasBin: true
 
@@ -4914,10 +4827,6 @@ packages:
     resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
     engines: {node: '>=6'}
 
-  pretty-format@29.7.0:
-    resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
   pretty-hrtime@1.0.3:
     resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==}
     engines: {node: '>= 0.8'}
@@ -4945,9 +4854,8 @@ packages:
     resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
     engines: {node: '>=10'}
 
-  promise.allsettled@1.0.7:
-    resolution: {integrity: sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA==}
-    engines: {node: '>= 0.4'}
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
 
   proto-list@1.2.4:
     resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -4962,24 +4870,14 @@ packages:
     resolution: {integrity: sha512-hNp56d5uuREVde7UqP+dmBkwzxrhJwYU5nL/mdivyFfkRZdgAgojkyBeU3jKo7ZHrjdSx6Q1CwUmYJI6INt20g==}
     hasBin: true
 
-  protocols@2.0.1:
-    resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
-
-  proxy-agent@6.4.0:
-    resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==}
-    engines: {node: '>= 14'}
-
-  proxy-from-env@1.1.0:
-    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
-
-  psl@1.9.0:
-    resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
+  psl@1.15.0:
+    resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
 
   public-encrypt@4.0.3:
     resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}
 
-  pump@3.0.0:
-    resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+  pump@3.0.3:
+    resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
 
   pumpify@2.0.1:
     resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==}
@@ -4995,12 +4893,8 @@ packages:
     resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
     engines: {node: '>=8'}
 
-  pupa@3.1.0:
-    resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==}
-    engines: {node: '>=12.20'}
-
-  qs@6.12.1:
-    resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==}
+  qs@6.14.0:
+    resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
     engines: {node: '>=0.6'}
 
   qs@6.5.3:
@@ -5028,9 +4922,6 @@ packages:
     resolution: {integrity: sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ==}
     hasBin: true
 
-  rambda@9.2.0:
-    resolution: {integrity: sha512-RjM8TBNPR+iSvWLqbBpFveDfEf2RPRKHuwBHjQdXsYFDwn3MIvgmJiqVVC1CIQKnOwzeDQd44zqDFgSKQ7RT1Q==}
-
   randombytes@2.1.0:
     resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
 
@@ -5045,8 +4936,8 @@ packages:
     resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
     hasBin: true
 
-  react-is@18.3.1:
-    resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
 
   read-cache@1.0.0:
     resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -5065,10 +4956,6 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
-  rechoir@0.6.2:
-    resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
-    engines: {node: '>= 0.10'}
-
   rechoir@0.8.0:
     resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
     engines: {node: '>= 10.13.0'}
@@ -5076,33 +4963,28 @@ packages:
   reflect-metadata@0.2.2:
     resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
 
-  regexp.prototype.flags@1.5.2:
-    resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
+  reflect.getprototypeof@1.0.10:
+    resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+    engines: {node: '>= 0.4'}
+
+  regexp.prototype.flags@1.5.4:
+    resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
     engines: {node: '>= 0.4'}
 
   registry-auth-token@4.2.2:
     resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==}
     engines: {node: '>=6.0.0'}
 
-  registry-auth-token@5.0.2:
-    resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==}
-    engines: {node: '>=14'}
-
   registry-url@5.1.0:
     resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==}
     engines: {node: '>=8'}
 
-  registry-url@6.0.1:
-    resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==}
-    engines: {node: '>=12'}
-
   reinterval@1.1.0:
     resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==}
 
-  release-it@17.3.0:
-    resolution: {integrity: sha512-7t9a2WEwqQKCdteshZUrO/3RX60plS5CzYAFr5+4Zj8qvRx1pFOFVglJSz4BeFAEd2yejpPxfI60+qRUzLEDZw==}
-    engines: {node: ^18.18.0 || ^20.8.0 || ^22.0.0}
-    hasBin: true
+  repeat-string@1.6.1:
+    resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
+    engines: {node: '>=0.10'}
 
   request@2.88.2:
     resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
@@ -5137,11 +5019,13 @@ packages:
   resolve-pkg-maps@1.0.0:
     resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
 
-  resolve@1.19.0:
-    resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==}
+  resolve@1.22.10:
+    resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+    engines: {node: '>= 0.4'}
+    hasBin: true
 
-  resolve@1.22.8:
-    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+  resolve@2.0.0-next.5:
+    resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
     hasBin: true
 
   responselike@3.0.0:
@@ -5156,9 +5040,9 @@ packages:
     resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
     engines: {node: '>=8'}
 
-  restore-cursor@4.0.0:
-    resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  restore-cursor@5.1.0:
+    resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+    engines: {node: '>=18'}
 
   retimer@3.0.0:
     resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==}
@@ -5167,49 +5051,41 @@ packages:
     resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
     engines: {node: '>= 4'}
 
-  retry@0.13.1:
-    resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
-    engines: {node: '>= 4'}
-
-  reusify@1.0.4:
-    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+  reusify@1.1.0:
+    resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
-  rfdc@1.3.1:
-    resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
+  rfdc@1.4.1:
+    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
 
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
-  rimraf@5.0.7:
-    resolution: {integrity: sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==}
-    engines: {node: '>=14.18'}
+  rimraf@6.0.1:
+    resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
+    engines: {node: 20 || >=22}
     hasBin: true
 
+  ripemd160@2.0.1:
+    resolution: {integrity: sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==}
+
   ripemd160@2.0.2:
     resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==}
 
-  rollup@4.17.2:
-    resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
+  rollup@4.44.1:
+    resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
-  rrweb-cssom@0.6.0:
-    resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
-
-  run-applescript@7.0.0:
-    resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
-    engines: {node: '>=18'}
+  rrweb-cssom@0.8.0:
+    resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
 
   run-async@2.4.1:
     resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
     engines: {node: '>=0.12.0'}
 
-  run-async@3.0.0:
-    resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
-    engines: {node: '>=0.12.0'}
-
   run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
@@ -5217,11 +5093,8 @@ packages:
     resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
     engines: {npm: '>=2.0.0'}
 
-  rxjs@7.8.1:
-    resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
-
-  safe-array-concat@1.1.2:
-    resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
+  safe-array-concat@1.1.3:
+    resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
     engines: {node: '>=0.4'}
 
   safe-buffer@5.1.2:
@@ -5230,12 +5103,16 @@ packages:
   safe-buffer@5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
 
-  safe-regex-test@1.0.3:
-    resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
+  safe-push-apply@1.0.0:
+    resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+    engines: {node: '>= 0.4'}
+
+  safe-regex-test@1.1.0:
+    resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
     engines: {node: '>= 0.4'}
 
-  safe-stable-stringify@2.4.3:
-    resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
+  safe-stable-stringify@2.5.0:
+    resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
     engines: {node: '>=10'}
 
   safer-buffer@2.1.2:
@@ -5255,22 +5132,18 @@ packages:
     resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==}
     engines: {node: '>=8'}
 
-  semver-diff@4.0.0:
-    resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==}
-    engines: {node: '>=12'}
-
-  semver@7.6.2:
-    resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
+  semver@7.7.2:
+    resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
     engines: {node: '>=10'}
     hasBin: true
 
-  send@0.18.0:
-    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
-    engines: {node: '>= 0.8.0'}
+  send@1.2.0:
+    resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
+    engines: {node: '>= 18'}
 
-  serve-static@1.15.0:
-    resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
-    engines: {node: '>= 0.8.0'}
+  serve-static@2.2.0:
+    resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
+    engines: {node: '>= 18'}
 
   set-blocking@2.0.0:
     resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
@@ -5283,11 +5156,16 @@ packages:
     resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
     engines: {node: '>= 0.4'}
 
+  set-proto@1.0.0:
+    resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+    engines: {node: '>= 0.4'}
+
   setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
 
-  sha.js@2.4.11:
-    resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
+  sha.js@2.4.12:
+    resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==}
+    engines: {node: '>= 0.10'}
     hasBin: true
 
   shallow-copy@0.0.1:
@@ -5304,20 +5182,28 @@ packages:
     resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
     engines: {node: '>=8'}
 
-  shell-quote@1.8.1:
-    resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
-
-  shelljs@0.8.5:
-    resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
-    engines: {node: '>=4'}
-    hasBin: true
+  shell-quote@1.8.3:
+    resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
+    engines: {node: '>= 0.4'}
 
   showdown@1.9.1:
     resolution: {integrity: sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==}
     hasBin: true
 
-  side-channel@1.0.6:
-    resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+  side-channel-list@1.0.0:
+    resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+    engines: {node: '>= 0.4'}
+
+  side-channel-map@1.0.1:
+    resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+    engines: {node: '>= 0.4'}
+
+  side-channel-weakmap@1.0.2:
+    resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+    engines: {node: '>= 0.4'}
+
+  side-channel@1.1.0:
+    resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
     engines: {node: '>= 0.4'}
 
   siginfo@2.0.0:
@@ -5352,10 +5238,6 @@ packages:
     resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
     engines: {node: '>=8'}
 
-  slash@5.1.0:
-    resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
-    engines: {node: '>=14.16'}
-
   slice-ansi@5.0.0:
     resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
     engines: {node: '>=12'}
@@ -5372,19 +5254,15 @@ packages:
     resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
     engines: {node: '>= 10'}
 
-  socks-proxy-agent@8.0.3:
-    resolution: {integrity: sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==}
-    engines: {node: '>= 14'}
-
-  socks@2.8.3:
-    resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
+  socks@2.8.5:
+    resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==}
     engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
 
   sonic-boom@1.4.1:
     resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==}
 
-  source-map-js@1.2.0:
-    resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
 
   source-map-support@0.5.21:
@@ -5411,8 +5289,8 @@ packages:
   spdx-expression-parse@4.0.0:
     resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==}
 
-  spdx-license-ids@3.0.17:
-    resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
+  spdx-license-ids@3.0.21:
+    resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==}
 
   split2@4.2.0:
     resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
@@ -5441,13 +5319,16 @@ packages:
     resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
     engines: {node: '>= 8'}
 
+  stable-hash-x@0.2.0:
+    resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
+    engines: {node: '>=12.0.0'}
+
+  stable-hash@0.0.5:
+    resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+
   stack-trace@0.0.10:
     resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
 
-  stack-utils@2.0.6:
-    resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
-    engines: {node: '>=10'}
-
   stackback@0.0.2:
     resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
 
@@ -5461,15 +5342,15 @@ packages:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
 
-  std-env@3.7.0:
-    resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
+  statuses@2.0.2:
+    resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+    engines: {node: '>= 0.8'}
 
-  stdin-discarder@0.2.2:
-    resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
-    engines: {node: '>=18'}
+  std-env@3.9.0:
+    resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
 
-  stop-iteration-iterator@1.0.0:
-    resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
+  stop-iteration-iterator@1.1.0:
+    resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
     engines: {node: '>= 0.4'}
 
   stream-browserify@3.0.0:
@@ -5521,16 +5402,24 @@ packages:
     resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
     engines: {node: '>=12'}
 
-  string-width@7.1.0:
-    resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==}
+  string-width@7.2.0:
+    resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
     engines: {node: '>=18'}
 
-  string.prototype.trim@1.2.9:
-    resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
+  string.prototype.matchall@4.0.12:
+    resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+    engines: {node: '>= 0.4'}
+
+  string.prototype.repeat@1.0.0:
+    resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+  string.prototype.trim@1.2.10:
+    resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
     engines: {node: '>= 0.4'}
 
-  string.prototype.trimend@1.0.8:
-    resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==}
+  string.prototype.trimend@1.0.9:
+    resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+    engines: {node: '>= 0.4'}
 
   string.prototype.trimstart@1.0.8:
     resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
@@ -5570,10 +5459,6 @@ packages:
     resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
     engines: {node: '>=6'}
 
-  strip-final-newline@3.0.0:
-    resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
-    engines: {node: '>=12'}
-
   strip-json-comments@2.0.1:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
@@ -5582,8 +5467,8 @@ packages:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
 
-  strip-literal@2.1.0:
-    resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
+  strip-literal@3.0.0:
+    resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
 
   subarg@1.0.0:
     resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==}
@@ -5607,14 +5492,11 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  svg-tags@1.0.0:
-    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
-
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
-  synckit@0.8.8:
-    resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==}
+  synckit@0.11.8:
+    resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==}
     engines: {node: ^14.18.0 || >=16.0.0}
 
   syntax-error@1.4.0:
@@ -5623,12 +5505,12 @@ packages:
   tachyons@4.12.0:
     resolution: {integrity: sha512-2nA2IrYFy3raCM9fxJ2KODRGHVSZNTW3BR0YnlGsLUf1DA3pk3YfWZ/DdfbnZK6zLZS+jUenlUGJsKcA5fUiZg==}
 
-  tapable@2.2.1:
-    resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+  tapable@2.2.2:
+    resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
     engines: {node: '>=6'}
 
-  tar-fs@2.1.1:
-    resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
+  tar-fs@2.1.3:
+    resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==}
 
   tar-stream@2.2.0:
     resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
@@ -5638,8 +5520,8 @@ packages:
     resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
     engines: {node: '>=10'}
 
-  tar@7.1.0:
-    resolution: {integrity: sha512-ENhg4W6BmjYxl8GTaE7/h99f0aXiSWv4kikRZ9n2/JRxypZniE84ILZqimAhxxX7Zb8Px6pFdheW3EeHfhnXQQ==}
+  tar@7.4.3:
+    resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
     engines: {node: '>=18'}
 
   tarn@3.0.2:
@@ -5651,9 +5533,9 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  test-exclude@6.0.0:
-    resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
-    engines: {node: '>=8'}
+  test-exclude@7.0.1:
+    resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
+    engines: {node: '>=18'}
 
   text-extensions@2.4.0:
     resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
@@ -5662,9 +5544,6 @@ packages:
   text-hex@1.0.0:
     resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
 
-  text-table@0.2.0:
-    resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
-
   through2@2.0.5:
     resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
 
@@ -5689,24 +5568,38 @@ packages:
     resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==}
     engines: {node: '>=8'}
 
-  tinybench@2.8.0:
-    resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
+  tinybench@2.9.0:
+    resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+  tinyexec@0.3.2:
+    resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+  tinyexec@1.0.1:
+    resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
+
+  tinyglobby@0.2.14:
+    resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
+    engines: {node: '>=12.0.0'}
+
+  tinypool@1.1.1:
+    resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+    engines: {node: ^18.0.0 || >=20.0.0}
 
-  tinypool@0.8.4:
-    resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+  tinyrainbow@2.0.0:
+    resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
     engines: {node: '>=14.0.0'}
 
-  tinyspy@2.2.1:
-    resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+  tinyspy@4.0.3:
+    resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
     engines: {node: '>=14.0.0'}
 
   tmp@0.0.33:
     resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
     engines: {node: '>=0.6.0'}
 
-  to-fast-properties@2.0.0:
-    resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
-    engines: {node: '>=4'}
+  to-buffer@1.2.1:
+    resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==}
+    engines: {node: '>= 0.4'}
 
   to-regex-range@5.0.1:
     resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@@ -5726,12 +5619,8 @@ packages:
   tr46@0.0.3:
     resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
 
-  tr46@4.1.1:
-    resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
-    engines: {node: '>=14'}
-
-  tr46@5.0.0:
-    resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
+  tr46@5.1.1:
+    resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
     engines: {node: '>=18'}
 
   transform-ast@2.4.4:
@@ -5741,14 +5630,19 @@ packages:
     resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
     engines: {node: '>= 14.0.0'}
 
-  ts-api-utils@1.3.0:
-    resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
-    engines: {node: '>=16'}
+  ts-api-utils@2.1.0:
+    resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+    engines: {node: '>=18.12'}
     peerDependencies:
-      typescript: '>=4.2.0'
+      typescript: '>=4.8.4'
 
-  ts-morph@22.0.0:
-    resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==}
+  ts-declaration-location@1.0.7:
+    resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==}
+    peerDependencies:
+      typescript: '>=4.0.0'
+
+  ts-morph@26.0.0:
+    resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==}
 
   ts-node@10.9.2:
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
@@ -5764,9 +5658,6 @@ packages:
       '@swc/wasm':
         optional: true
 
-  tsconfig-paths@3.15.0:
-    resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
-
   tsconfig-paths@4.2.0:
     resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
     engines: {node: '>=6'}
@@ -5774,11 +5665,11 @@ packages:
   tslib@1.14.1:
     resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
 
-  tslib@2.6.2:
-    resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
-  tsx@4.10.5:
-    resolution: {integrity: sha512-twDSbf7Gtea4I2copqovUiNTEDrT8XNFXsuHpfGbdpW/z9ZW4fTghzzhAG0WfrCuJmJiOEY1nLIjq4u3oujRWQ==}
+  tsx@4.20.3:
+    resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
     engines: {node: '>=18.0.0'}
     hasBin: true
 
@@ -5808,43 +5699,27 @@ packages:
   type-component@0.0.1:
     resolution: {integrity: sha512-mDZRBQS2yZkwRQKfjJvQ8UIYJeBNNWCq+HBNstl9N5s9jZ4dkVYXEGkVPsSCEh5Ld4JM1kmrZTzjnrqSAIQ7dw==}
 
-  type-detect@4.0.8:
-    resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
-    engines: {node: '>=4'}
-
   type-fest@0.20.2:
     resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
     engines: {node: '>=10'}
 
-  type-fest@0.21.3:
-    resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
-    engines: {node: '>=10'}
-
-  type-fest@1.4.0:
-    resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
-    engines: {node: '>=10'}
-
-  type-fest@2.19.0:
-    resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
-    engines: {node: '>=12.20'}
-
-  type@2.7.2:
-    resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
+  type@2.7.3:
+    resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
 
-  typed-array-buffer@1.0.2:
-    resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
+  typed-array-buffer@1.0.3:
+    resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
     engines: {node: '>= 0.4'}
 
-  typed-array-byte-length@1.0.1:
-    resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==}
+  typed-array-byte-length@1.0.3:
+    resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
     engines: {node: '>= 0.4'}
 
-  typed-array-byte-offset@1.0.2:
-    resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
+  typed-array-byte-offset@1.0.4:
+    resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
     engines: {node: '>= 0.4'}
 
-  typed-array-length@1.0.6:
-    resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
+  typed-array-length@1.0.7:
+    resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
     engines: {node: '>= 0.4'}
 
   typedarray-pool@1.2.0:
@@ -5856,32 +5731,35 @@ packages:
   typedarray@0.0.6:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
 
-  typescript@5.4.5:
-    resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
-    engines: {node: '>=14.17'}
-    hasBin: true
-
-  ufo@1.5.3:
-    resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
+  typescript-eslint@8.35.1:
+    resolution: {integrity: sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '>=4.8.4 <5.9.0'
 
-  uglify-js@3.17.4:
-    resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
-    engines: {node: '>=0.8.0'}
+  typescript@5.8.3:
+    resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
+    engines: {node: '>=14.17'}
     hasBin: true
 
   umd@3.0.3:
     resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==}
     hasBin: true
 
-  unbox-primitive@1.0.2:
-    resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+  unbox-primitive@1.1.0:
+    resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+    engines: {node: '>= 0.4'}
 
   undeclared-identifiers@1.1.3:
     resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==}
     hasBin: true
 
-  undici-types@5.26.5:
-    resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+  undici-types@6.21.0:
+    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+  undici-types@7.8.0:
+    resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
 
   unicorn-magic@0.1.0:
     resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
@@ -5900,13 +5778,6 @@ packages:
     resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
     engines: {node: '>=8'}
 
-  unique-string@3.0.0:
-    resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==}
-    engines: {node: '>=12'}
-
-  universal-user-agent@6.0.1:
-    resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
-
   universalify@0.2.0:
     resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
     engines: {node: '>= 4.0.0'}
@@ -5915,12 +5786,11 @@ packages:
     resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
     engines: {node: '>= 10.0.0'}
 
-  unpipe@1.0.0:
-    resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
-    engines: {node: '>= 0.8'}
+  unrs-resolver@1.10.1:
+    resolution: {integrity: sha512-EFrL7Hw4kmhZdwWO3dwwFJo6hO3FXuQ6Bg8BK/faHZ9m1YxqBS31BNSTxklIQkxK/4LlV8zTYnPsIRLBzTzjCA==}
 
-  update-browserslist-db@1.0.16:
-    resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
+  update-browserslist-db@1.1.3:
+    resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
     hasBin: true
     peerDependencies:
       browserslist: '>= 4.21.0'
@@ -5929,28 +5799,21 @@ packages:
     resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==}
     engines: {node: '>=10'}
 
-  update-notifier@7.0.0:
-    resolution: {integrity: sha512-Hv25Bh+eAbOLlsjJreVPOs4vd51rrtCrmhyOJtbpAojro34jS4KQaEp4/EvlHJX7jSO42VvEFpkastVyXyIsdQ==}
-    engines: {node: '>=18'}
-
   upper-case@1.1.3:
     resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
 
   uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
 
-  url-join@5.0.0:
-    resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   url-parse@1.5.10:
     resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
 
-  url@0.11.3:
-    resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==}
+  url@0.11.4:
+    resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
+    engines: {node: '>= 0.4'}
 
-  utf-8-validate@6.0.4:
-    resolution: {integrity: sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==}
+  utf-8-validate@6.0.5:
+    resolution: {integrity: sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==}
     engines: {node: '>=6.14.2'}
 
   util-deprecate@1.0.2:
@@ -5975,8 +5838,8 @@ packages:
   v8-compile-cache-lib@3.0.1:
     resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
 
-  v8-to-istanbul@9.2.0:
-    resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
+  v8-to-istanbul@9.3.0:
+    resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
     engines: {node: '>=10.12.0'}
 
   varint@5.0.0:
@@ -5989,53 +5852,68 @@ packages:
     resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
     engines: {'0': node >=0.6.0}
 
-  vite-node@1.6.0:
-    resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  vite-node@3.2.4:
+    resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
 
-  vite@5.2.11:
-    resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  vite@7.0.2:
+    resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==}
+    engines: {node: ^20.19.0 || >=22.12.0}
     hasBin: true
     peerDependencies:
-      '@types/node': ^18.0.0 || >=20.0.0
-      less: '*'
+      '@types/node': ^20.19.0 || >=22.12.0
+      jiti: '>=1.21.0'
+      less: ^4.0.0
       lightningcss: ^1.21.0
-      sass: '*'
-      stylus: '*'
-      sugarss: '*'
-      terser: ^5.4.0
+      sass: ^1.70.0
+      sass-embedded: ^1.70.0
+      stylus: '>=0.54.8'
+      sugarss: ^5.0.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
     peerDependenciesMeta:
       '@types/node':
         optional: true
+      jiti:
+        optional: true
       less:
         optional: true
       lightningcss:
         optional: true
       sass:
         optional: true
+      sass-embedded:
+        optional: true
       stylus:
         optional: true
       sugarss:
         optional: true
       terser:
         optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
 
-  vitest@1.6.0:
-    resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  vitest@3.2.4:
+    resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
     peerDependencies:
       '@edge-runtime/vm': '*'
-      '@types/node': ^18.0.0 || >=20.0.0
-      '@vitest/browser': 1.6.0
-      '@vitest/ui': 1.6.0
+      '@types/debug': ^4.1.12
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      '@vitest/browser': 3.2.4
+      '@vitest/ui': 3.2.4
       happy-dom: '*'
       jsdom: '*'
     peerDependenciesMeta:
       '@edge-runtime/vm':
         optional: true
+      '@types/debug':
+        optional: true
       '@types/node':
         optional: true
       '@vitest/browser':
@@ -6050,28 +5928,34 @@ packages:
   vm-browserify@1.1.2:
     resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
 
-  vue-component-type-helpers@2.0.19:
-    resolution: {integrity: sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==}
+  vscode-languageserver-textdocument@1.0.12:
+    resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==}
+
+  vscode-uri@3.1.0:
+    resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
+  vue-component-type-helpers@2.2.12:
+    resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
 
-  vue-eslint-parser@9.4.2:
-    resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==}
-    engines: {node: ^14.17.0 || >=16.0.0}
+  vue-eslint-parser@10.2.0:
+    resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
-      eslint: '>=6.0.0'
+      eslint: ^8.57.0 || ^9.0.0
 
-  vue-router@4.3.2:
-    resolution: {integrity: sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==}
+  vue-router@4.5.1:
+    resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
     peerDependencies:
       vue: ^3.2.0
 
-  vue-toast-notification@3.1.2:
-    resolution: {integrity: sha512-oNRL/W9aaHoeScp+iTIW7k09vM16/+8aptp2maa+7qTB43JuxmAgKdXKFYtf+uvSNOYYq2BIWgLCeJ61pwom/A==}
+  vue-toast-notification@3.1.3:
+    resolution: {integrity: sha512-XNyWqwLIGBFfX5G9sK+clq3N3IPlhDjzNdbZaXkEElcotPlWs0wWZailk1vqhdtLYT/93Y4FHAVuzyatLmPZRA==}
     engines: {node: '>=12.15.0'}
     peerDependencies:
       vue: ^3.0
 
-  vue@3.4.27:
-    resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==}
+  vue@3.5.17:
+    resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
@@ -6085,10 +5969,6 @@ packages:
   wcwidth@1.0.1:
     resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
 
-  web-streams-polyfill@3.3.3:
-    resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
-    engines: {node: '>= 8'}
-
   webfontloader@1.6.28:
     resolution: {integrity: sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==}
 
@@ -6107,25 +5987,30 @@ packages:
     resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
     engines: {node: '>=18'}
 
-  whatwg-url@13.0.0:
-    resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==}
-    engines: {node: '>=16'}
-
-  whatwg-url@14.0.0:
-    resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
+  whatwg-url@14.2.0:
+    resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
     engines: {node: '>=18'}
 
   whatwg-url@5.0.0:
     resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
 
-  which-boxed-primitive@1.0.2:
-    resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+  which-boxed-primitive@1.1.1:
+    resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+    engines: {node: '>= 0.4'}
+
+  which-builtin-type@1.2.1:
+    resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+    engines: {node: '>= 0.4'}
+
+  which-collection@1.0.2:
+    resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+    engines: {node: '>= 0.4'}
 
   which-module@2.0.1:
     resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
 
-  which-typed-array@1.1.15:
-    resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
+  which-typed-array@1.1.19:
+    resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
     engines: {node: '>= 0.4'}
 
   which@2.0.2:
@@ -6133,8 +6018,8 @@ packages:
     engines: {node: '>= 8'}
     hasBin: true
 
-  why-is-node-running@2.2.2:
-    resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
+  why-is-node-running@2.3.0:
+    resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
     engines: {node: '>=8'}
     hasBin: true
 
@@ -6145,50 +6030,32 @@ packages:
     resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
     engines: {node: '>=8'}
 
-  widest-line@4.0.1:
-    resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
-    engines: {node: '>=12'}
-
-  wildcard-match@5.1.3:
-    resolution: {integrity: sha512-a95hPUk+BNzSGLntNXYxsjz2Hooi5oL7xOfJR6CKwSsSALh7vUNuTlzsrZowtYy38JNduYFRVhFv19ocqNOZlg==}
-
   windows-release@4.0.0:
     resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==}
     engines: {node: '>=10'}
 
-  windows-release@5.1.1:
-    resolution: {integrity: sha512-NMD00arvqcq2nwqc5Q6KtrSRHK+fVD31erE5FEMahAw5PmVCgD7MUXodq3pdZSUkqA9Cda2iWx6s1XYwiJWRmw==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
   winston-daily-rotate-file@5.0.0:
     resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==}
     engines: {node: '>=8'}
     peerDependencies:
       winston: ^3
 
-  winston-transport@4.7.0:
-    resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==}
+  winston-transport@4.9.0:
+    resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
     engines: {node: '>= 12.0.0'}
 
-  winston@3.13.0:
-    resolution: {integrity: sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==}
+  winston@3.17.0:
+    resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==}
     engines: {node: '>= 12.0.0'}
 
   word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
 
-  wordwrap@1.0.0:
-    resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
-
   wrap-ansi@5.1.0:
     resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
     engines: {node: '>=6'}
 
-  wrap-ansi@6.2.0:
-    resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
-    engines: {node: '>=8'}
-
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
@@ -6207,8 +6074,8 @@ packages:
   write-file-atomic@3.0.3:
     resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
 
-  ws@8.17.0:
-    resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==}
+  ws@8.18.3:
+    resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
       bufferutil: ^4.0.1
@@ -6259,9 +6126,10 @@ packages:
     resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
     engines: {node: '>=18'}
 
-  yaml@2.3.4:
-    resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
-    engines: {node: '>= 14'}
+  yaml@2.8.0:
+    resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
+    engines: {node: '>= 14.6'}
+    hasBin: true
 
   yargs-parser@15.0.3:
     resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==}
@@ -6285,21 +6153,21 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
-  yocto-queue@1.0.0:
-    resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
+  yocto-queue@1.2.1:
+    resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
     engines: {node: '>=12.20'}
 
 snapshots:
 
-  0x@5.7.0:
+  0x@5.8.0:
     dependencies:
-      ajv: 8.13.0
-      browserify: 17.0.0
+      ajv: 8.17.1
+      browserify: 17.0.1
       concat-stream: 2.0.0
       d3-fg: 6.14.0
       debounce: 1.2.1
-      debug: 4.3.4
-      end-of-stream: 1.4.4
+      debug: 4.4.1
+      end-of-stream: 1.4.5
       env-string: 1.0.1
       escape-string-regexp: 4.0.0
       execspawn: 1.0.1
@@ -6309,13 +6177,13 @@ snapshots:
       jsonstream2: 3.0.0
       make-dir: 3.1.0
       minimist: 1.2.8
-      morphdom: 2.7.2
+      morphdom: 2.7.5
       nanohtml: 1.10.0
       on-net-listen: 1.1.2
       opn: 5.5.0
-      pump: 3.0.0
+      pump: 3.0.3
       pumpify: 2.0.1
-      semver: 7.6.2
+      semver: 7.7.2
       single-line-log: 1.1.2
       split2: 4.2.0
       tachyons: 4.12.0
@@ -6326,198 +6194,187 @@ snapshots:
 
   '@ampproject/remapping@2.3.0':
     dependencies:
-      '@jridgewell/gen-mapping': 0.3.5
-      '@jridgewell/trace-mapping': 0.3.25
+      '@jridgewell/gen-mapping': 0.3.12
+      '@jridgewell/trace-mapping': 0.3.29
+
+  '@asamuzakjp/css-color@3.2.0':
+    dependencies:
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+      lru-cache: 10.4.3
 
   '@assemblyscript/loader@0.19.23': {}
 
-  '@babel/code-frame@7.24.2':
+  '@babel/code-frame@7.27.1':
     dependencies:
-      '@babel/highlight': 7.24.5
-      picocolors: 1.0.1
+      '@babel/helper-validator-identifier': 7.27.1
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
 
-  '@babel/compat-data@7.24.4': {}
+  '@babel/compat-data@7.28.0': {}
 
-  '@babel/core@7.24.5':
+  '@babel/core@7.28.0':
     dependencies:
       '@ampproject/remapping': 2.3.0
-      '@babel/code-frame': 7.24.2
-      '@babel/generator': 7.24.5
-      '@babel/helper-compilation-targets': 7.23.6
-      '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5)
-      '@babel/helpers': 7.24.5
-      '@babel/parser': 7.24.5
-      '@babel/template': 7.24.0
-      '@babel/traverse': 7.24.5
-      '@babel/types': 7.24.5
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.0
+      '@babel/helper-compilation-targets': 7.27.2
+      '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
+      '@babel/helpers': 7.27.6
+      '@babel/parser': 7.28.0
+      '@babel/template': 7.27.2
+      '@babel/traverse': 7.28.0
+      '@babel/types': 7.28.0
       convert-source-map: 2.0.0
-      debug: 4.3.4
+      debug: 4.4.1
       gensync: 1.0.0-beta.2
       json5: 2.2.3
-      semver: 7.6.2
+      semver: 7.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/generator@7.24.5':
+  '@babel/generator@7.28.0':
     dependencies:
-      '@babel/types': 7.24.5
-      '@jridgewell/gen-mapping': 0.3.5
-      '@jridgewell/trace-mapping': 0.3.25
-      jsesc: 2.5.2
+      '@babel/parser': 7.28.0
+      '@babel/types': 7.28.0
+      '@jridgewell/gen-mapping': 0.3.12
+      '@jridgewell/trace-mapping': 0.3.29
+      jsesc: 3.1.0
 
-  '@babel/helper-annotate-as-pure@7.22.5':
+  '@babel/helper-annotate-as-pure@7.27.3':
     dependencies:
-      '@babel/types': 7.24.5
+      '@babel/types': 7.28.0
 
-  '@babel/helper-compilation-targets@7.23.6':
+  '@babel/helper-compilation-targets@7.27.2':
     dependencies:
-      '@babel/compat-data': 7.24.4
-      '@babel/helper-validator-option': 7.23.5
-      browserslist: 4.23.0
+      '@babel/compat-data': 7.28.0
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.25.1
       lru-cache: 5.1.1
-      semver: 7.6.2
-
-  '@babel/helper-create-class-features-plugin@7.24.5(@babel/core@7.24.5)':
-    dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-annotate-as-pure': 7.22.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-function-name': 7.23.0
-      '@babel/helper-member-expression-to-functions': 7.24.5
-      '@babel/helper-optimise-call-expression': 7.22.5
-      '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5)
-      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
-      '@babel/helper-split-export-declaration': 7.24.5
-      semver: 7.6.2
-
-  '@babel/helper-environment-visitor@7.22.20': {}
-
-  '@babel/helper-function-name@7.23.0':
-    dependencies:
-      '@babel/template': 7.24.0
-      '@babel/types': 7.24.5
-
-  '@babel/helper-hoist-variables@7.22.5':
-    dependencies:
-      '@babel/types': 7.24.5
-
-  '@babel/helper-member-expression-to-functions@7.24.5':
-    dependencies:
-      '@babel/types': 7.24.5
+      semver: 7.7.2
+
+  '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)':
+    dependencies:
+      '@babel/core': 7.28.0
+      '@babel/helper-annotate-as-pure': 7.27.3
+      '@babel/helper-member-expression-to-functions': 7.27.1
+      '@babel/helper-optimise-call-expression': 7.27.1
+      '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0)
+      '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+      '@babel/traverse': 7.28.0
+      semver: 7.7.2
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-module-imports@7.22.15':
-    dependencies:
-      '@babel/types': 7.24.5
+  '@babel/helper-globals@7.28.0': {}
 
-  '@babel/helper-module-imports@7.24.3':
+  '@babel/helper-member-expression-to-functions@7.27.1':
     dependencies:
-      '@babel/types': 7.24.5
+      '@babel/traverse': 7.28.0
+      '@babel/types': 7.28.0
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)':
+  '@babel/helper-module-imports@7.27.1':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-module-imports': 7.24.3
-      '@babel/helper-simple-access': 7.24.5
-      '@babel/helper-split-export-declaration': 7.24.5
-      '@babel/helper-validator-identifier': 7.24.5
+      '@babel/traverse': 7.28.0
+      '@babel/types': 7.28.0
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-optimise-call-expression@7.22.5':
+  '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)':
     dependencies:
-      '@babel/types': 7.24.5
-
-  '@babel/helper-plugin-utils@7.24.5': {}
+      '@babel/core': 7.28.0
+      '@babel/helper-module-imports': 7.27.1
+      '@babel/helper-validator-identifier': 7.27.1
+      '@babel/traverse': 7.28.0
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-replace-supers@7.24.1(@babel/core@7.24.5)':
+  '@babel/helper-optimise-call-expression@7.27.1':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-member-expression-to-functions': 7.24.5
-      '@babel/helper-optimise-call-expression': 7.22.5
+      '@babel/types': 7.28.0
 
-  '@babel/helper-simple-access@7.24.5':
-    dependencies:
-      '@babel/types': 7.24.5
+  '@babel/helper-plugin-utils@7.27.1': {}
 
-  '@babel/helper-skip-transparent-expression-wrappers@7.22.5':
+  '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)':
     dependencies:
-      '@babel/types': 7.24.5
+      '@babel/core': 7.28.0
+      '@babel/helper-member-expression-to-functions': 7.27.1
+      '@babel/helper-optimise-call-expression': 7.27.1
+      '@babel/traverse': 7.28.0
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-split-export-declaration@7.24.5':
+  '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
     dependencies:
-      '@babel/types': 7.24.5
-
-  '@babel/helper-string-parser@7.24.1': {}
+      '@babel/traverse': 7.28.0
+      '@babel/types': 7.28.0
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/helper-validator-identifier@7.24.5': {}
+  '@babel/helper-string-parser@7.27.1': {}
 
-  '@babel/helper-validator-option@7.23.5': {}
+  '@babel/helper-validator-identifier@7.27.1': {}
 
-  '@babel/helpers@7.24.5':
-    dependencies:
-      '@babel/template': 7.24.0
-      '@babel/traverse': 7.24.5
-      '@babel/types': 7.24.5
-    transitivePeerDependencies:
-      - supports-color
+  '@babel/helper-validator-option@7.27.1': {}
 
-  '@babel/highlight@7.24.5':
+  '@babel/helpers@7.27.6':
     dependencies:
-      '@babel/helper-validator-identifier': 7.24.5
-      chalk: 2.4.2
-      js-tokens: 4.0.0
-      picocolors: 1.0.1
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.0
 
-  '@babel/parser@7.24.5':
+  '@babel/parser@7.28.0':
     dependencies:
-      '@babel/types': 7.24.5
+      '@babel/types': 7.28.0
 
-  '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.5)':
+  '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-plugin-utils': 7.24.5
+      '@babel/core': 7.28.0
+      '@babel/helper-plugin-utils': 7.27.1
 
-  '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.5)':
+  '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-plugin-utils': 7.24.5
+      '@babel/core': 7.28.0
+      '@babel/helper-plugin-utils': 7.27.1
 
-  '@babel/plugin-transform-typescript@7.24.5(@babel/core@7.24.5)':
+  '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/helper-annotate-as-pure': 7.22.5
-      '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5)
-      '@babel/helper-plugin-utils': 7.24.5
-      '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.5)
+      '@babel/core': 7.28.0
+      '@babel/helper-annotate-as-pure': 7.27.3
+      '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
+      '@babel/helper-plugin-utils': 7.27.1
+      '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+      '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0)
+    transitivePeerDependencies:
+      - supports-color
 
-  '@babel/template@7.24.0':
+  '@babel/template@7.27.2':
     dependencies:
-      '@babel/code-frame': 7.24.2
-      '@babel/parser': 7.24.5
-      '@babel/types': 7.24.5
+      '@babel/code-frame': 7.27.1
+      '@babel/parser': 7.28.0
+      '@babel/types': 7.28.0
 
-  '@babel/traverse@7.24.5':
+  '@babel/traverse@7.28.0':
     dependencies:
-      '@babel/code-frame': 7.24.2
-      '@babel/generator': 7.24.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-function-name': 7.23.0
-      '@babel/helper-hoist-variables': 7.22.5
-      '@babel/helper-split-export-declaration': 7.24.5
-      '@babel/parser': 7.24.5
-      '@babel/types': 7.24.5
-      debug: 4.3.4
-      globals: 11.12.0
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.0
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.28.0
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.0
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/types@7.24.5':
+  '@babel/types@7.28.0':
     dependencies:
-      '@babel/helper-string-parser': 7.24.1
-      '@babel/helper-validator-identifier': 7.24.5
-      to-fast-properties: 2.0.0
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.27.1
 
-  '@bcoe/v8-coverage@0.2.3': {}
+  '@bcoe/v8-coverage@1.0.2': {}
 
   '@clinic/bubbleprof@10.0.0':
     dependencies:
@@ -6525,7 +6382,7 @@ snapshots:
       '@clinic/node-trace-log-join': 2.0.0
       '@clinic/trace-events-parser': 2.0.0
       array-flatten: 3.0.0
-      async: 3.2.5
+      async: 3.2.6
       d3-axis: 1.0.12
       d3-color: 3.1.0
       d3-drag: 1.2.5
@@ -6544,17 +6401,17 @@ snapshots:
       mkdirp: 1.0.4
       on-net-listen: 1.1.2
       protocol-buffers: 4.2.0
-      pump: 3.0.0
+      pump: 3.0.3
 
   '@clinic/clinic-common@7.1.0':
     dependencies:
       brfs: 2.0.2
-      browserify: 17.0.0
+      browserify: 17.0.1
       chalk: 4.1.2
       lodash.debounce: 4.0.8
       loose-envify: 1.4.0
-      postcss: 8.4.38
-      postcss-import: 13.0.0(postcss@8.4.38)
+      postcss: 8.5.6
+      postcss-import: 13.0.0(postcss@8.5.6)
       stream-template: 0.0.10
       webfontloader: 1.6.28
 
@@ -6565,7 +6422,7 @@ snapshots:
       '@clinic/trace-events-parser': 2.0.0
       '@tensorflow/tfjs-backend-cpu': 3.21.0(@tensorflow/tfjs-core@3.21.0(encoding@0.1.13))
       '@tensorflow/tfjs-core': 3.21.0(encoding@0.1.13)
-      async: 3.2.5
+      async: 3.2.6
       clipboard-copy: 4.0.1
       d3-array: 2.12.1
       d3-axis: 1.0.12
@@ -6573,7 +6430,7 @@ snapshots:
       d3-selection: 1.4.2
       d3-shape: 1.3.7
       d3-time-format: 2.3.0
-      debug: 4.3.4
+      debug: 4.4.1
       distributions: 2.2.0
       endpoint: 0.4.5
       hidden-markov-model-tf: 4.0.0(@tensorflow/tfjs-core@3.21.0(encoding@0.1.13))
@@ -6581,9 +6438,9 @@ snapshots:
       mkdirp: 1.0.4
       on-net-listen: 1.1.2
       protocol-buffers: 4.2.0
-      pump: 3.0.0
+      pump: 3.0.3
       pumpify: 2.0.1
-      semver: 7.6.2
+      semver: 7.7.2
       showdown: 1.9.1
       stream-template: 0.0.10
       streaming-json-stringify: 3.1.0
@@ -6595,7 +6452,7 @@ snapshots:
 
   '@clinic/flame@13.0.0':
     dependencies:
-      0x: 5.7.0
+      0x: 5.8.0
       '@clinic/clinic-common': 7.1.0
       copy-to-clipboard: 3.3.3
       d3-array: 2.12.1
@@ -6603,7 +6460,7 @@ snapshots:
       d3-selection: 1.4.2
       flame-gradient: 1.0.0
       lodash.debounce: 4.0.8
-      pump: 3.0.0
+      pump: 3.0.3
       querystringify: 2.2.0
       rimraf: 3.0.2
     transitivePeerDependencies:
@@ -6618,10 +6475,10 @@ snapshots:
       d3-array: 2.12.1
       d3-fg: 6.14.0
       d3-selection: 1.4.2
-      fs-extra: 11.2.0
+      fs-extra: 11.3.0
       lodash.debounce: 4.0.8
       on-net-listen: 1.1.2
-      pump: 3.0.0
+      pump: 3.0.3
       querystringify: 2.2.0
       sinusoidal-decimal: 1.0.0
 
@@ -6629,7 +6486,7 @@ snapshots:
     dependencies:
       '@clinic/trace-events-parser': 2.0.0
       multistream: 2.1.1
-      pump: 3.0.0
+      pump: 3.0.3
       through2: 2.0.5
 
   '@clinic/trace-events-parser@2.0.0':
@@ -6641,66 +6498,66 @@ snapshots:
 
   '@colors/colors@1.6.0': {}
 
-  '@commitlint/cli@19.3.0(@types/node@20.12.12)(typescript@5.4.5)':
+  '@commitlint/cli@19.8.1(@types/node@24.0.10)(typescript@5.8.3)':
     dependencies:
-      '@commitlint/format': 19.3.0
-      '@commitlint/lint': 19.2.2
-      '@commitlint/load': 19.2.0(@types/node@20.12.12)(typescript@5.4.5)
-      '@commitlint/read': 19.2.1
-      '@commitlint/types': 19.0.3
-      execa: 8.0.1
+      '@commitlint/format': 19.8.1
+      '@commitlint/lint': 19.8.1
+      '@commitlint/load': 19.8.1(@types/node@24.0.10)(typescript@5.8.3)
+      '@commitlint/read': 19.8.1
+      '@commitlint/types': 19.8.1
+      tinyexec: 1.0.1
       yargs: 17.7.2
     transitivePeerDependencies:
       - '@types/node'
       - typescript
 
-  '@commitlint/config-conventional@19.2.2':
+  '@commitlint/config-conventional@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
+      '@commitlint/types': 19.8.1
       conventional-changelog-conventionalcommits: 7.0.2
 
-  '@commitlint/config-validator@19.0.3':
+  '@commitlint/config-validator@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
-      ajv: 8.13.0
+      '@commitlint/types': 19.8.1
+      ajv: 8.17.1
 
-  '@commitlint/ensure@19.0.3':
+  '@commitlint/ensure@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
+      '@commitlint/types': 19.8.1
       lodash.camelcase: 4.3.0
       lodash.kebabcase: 4.1.1
       lodash.snakecase: 4.1.1
       lodash.startcase: 4.4.0
       lodash.upperfirst: 4.3.1
 
-  '@commitlint/execute-rule@19.0.0': {}
+  '@commitlint/execute-rule@19.8.1': {}
 
-  '@commitlint/format@19.3.0':
+  '@commitlint/format@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
-      chalk: 5.3.0
+      '@commitlint/types': 19.8.1
+      chalk: 5.4.1
 
-  '@commitlint/is-ignored@19.2.2':
+  '@commitlint/is-ignored@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
-      semver: 7.6.2
+      '@commitlint/types': 19.8.1
+      semver: 7.7.2
 
-  '@commitlint/lint@19.2.2':
+  '@commitlint/lint@19.8.1':
     dependencies:
-      '@commitlint/is-ignored': 19.2.2
-      '@commitlint/parse': 19.0.3
-      '@commitlint/rules': 19.0.3
-      '@commitlint/types': 19.0.3
+      '@commitlint/is-ignored': 19.8.1
+      '@commitlint/parse': 19.8.1
+      '@commitlint/rules': 19.8.1
+      '@commitlint/types': 19.8.1
 
-  '@commitlint/load@19.2.0(@types/node@20.12.12)(typescript@5.4.5)':
+  '@commitlint/load@19.8.1(@types/node@24.0.10)(typescript@5.8.3)':
     dependencies:
-      '@commitlint/config-validator': 19.0.3
-      '@commitlint/execute-rule': 19.0.0
-      '@commitlint/resolve-extends': 19.1.0
-      '@commitlint/types': 19.0.3
-      chalk: 5.3.0
-      cosmiconfig: 9.0.0(typescript@5.4.5)
-      cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.12)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5)
+      '@commitlint/config-validator': 19.8.1
+      '@commitlint/execute-rule': 19.8.1
+      '@commitlint/resolve-extends': 19.8.1
+      '@commitlint/types': 19.8.1
+      chalk: 5.4.1
+      cosmiconfig: 9.0.0(typescript@5.8.3)
+      cosmiconfig-typescript-loader: 6.1.0(@types/node@24.0.10)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -6708,248 +6565,464 @@ snapshots:
       - '@types/node'
       - typescript
 
-  '@commitlint/message@19.0.0': {}
+  '@commitlint/message@19.8.1': {}
 
-  '@commitlint/parse@19.0.3':
+  '@commitlint/parse@19.8.1':
     dependencies:
-      '@commitlint/types': 19.0.3
+      '@commitlint/types': 19.8.1
       conventional-changelog-angular: 7.0.0
       conventional-commits-parser: 5.0.0
 
-  '@commitlint/read@19.2.1':
+  '@commitlint/read@19.8.1':
     dependencies:
-      '@commitlint/top-level': 19.0.0
-      '@commitlint/types': 19.0.3
-      execa: 8.0.1
+      '@commitlint/top-level': 19.8.1
+      '@commitlint/types': 19.8.1
       git-raw-commits: 4.0.0
       minimist: 1.2.8
+      tinyexec: 1.0.1
 
-  '@commitlint/resolve-extends@19.1.0':
+  '@commitlint/resolve-extends@19.8.1':
     dependencies:
-      '@commitlint/config-validator': 19.0.3
-      '@commitlint/types': 19.0.3
+      '@commitlint/config-validator': 19.8.1
+      '@commitlint/types': 19.8.1
       global-directory: 4.0.1
       import-meta-resolve: 4.1.0
       lodash.mergewith: 4.6.2
       resolve-from: 5.0.0
 
-  '@commitlint/rules@19.0.3':
+  '@commitlint/rules@19.8.1':
     dependencies:
-      '@commitlint/ensure': 19.0.3
-      '@commitlint/message': 19.0.0
-      '@commitlint/to-lines': 19.0.0
-      '@commitlint/types': 19.0.3
-      execa: 8.0.1
+      '@commitlint/ensure': 19.8.1
+      '@commitlint/message': 19.8.1
+      '@commitlint/to-lines': 19.8.1
+      '@commitlint/types': 19.8.1
 
-  '@commitlint/to-lines@19.0.0': {}
+  '@commitlint/to-lines@19.8.1': {}
 
-  '@commitlint/top-level@19.0.0':
+  '@commitlint/top-level@19.8.1':
     dependencies:
       find-up: 7.0.0
 
-  '@commitlint/types@19.0.3':
+  '@commitlint/types@19.8.1':
+    dependencies:
+      '@types/conventional-commits-parser': 5.0.1
+      chalk: 5.4.1
+
+  '@cspell/cspell-bundled-dicts@9.1.2':
+    dependencies:
+      '@cspell/dict-ada': 4.1.0
+      '@cspell/dict-al': 1.1.0
+      '@cspell/dict-aws': 4.0.10
+      '@cspell/dict-bash': 4.2.0
+      '@cspell/dict-companies': 3.2.1
+      '@cspell/dict-cpp': 6.0.8
+      '@cspell/dict-cryptocurrencies': 5.0.4
+      '@cspell/dict-csharp': 4.0.6
+      '@cspell/dict-css': 4.0.17
+      '@cspell/dict-dart': 2.3.0
+      '@cspell/dict-data-science': 2.0.8
+      '@cspell/dict-django': 4.1.4
+      '@cspell/dict-docker': 1.1.14
+      '@cspell/dict-dotnet': 5.0.9
+      '@cspell/dict-elixir': 4.0.7
+      '@cspell/dict-en-common-misspellings': 2.1.2
+      '@cspell/dict-en-gb-mit': 3.1.3
+      '@cspell/dict-en_us': 4.4.13
+      '@cspell/dict-filetypes': 3.0.12
+      '@cspell/dict-flutter': 1.1.0
+      '@cspell/dict-fonts': 4.0.4
+      '@cspell/dict-fsharp': 1.1.0
+      '@cspell/dict-fullstack': 3.2.6
+      '@cspell/dict-gaming-terms': 1.1.1
+      '@cspell/dict-git': 3.0.6
+      '@cspell/dict-golang': 6.0.22
+      '@cspell/dict-google': 1.0.8
+      '@cspell/dict-haskell': 4.0.5
+      '@cspell/dict-html': 4.0.11
+      '@cspell/dict-html-symbol-entities': 4.0.3
+      '@cspell/dict-java': 5.0.11
+      '@cspell/dict-julia': 1.1.0
+      '@cspell/dict-k8s': 1.0.11
+      '@cspell/dict-kotlin': 1.1.0
+      '@cspell/dict-latex': 4.0.3
+      '@cspell/dict-lorem-ipsum': 4.0.4
+      '@cspell/dict-lua': 4.0.7
+      '@cspell/dict-makefile': 1.0.4
+      '@cspell/dict-markdown': 2.0.11(@cspell/dict-css@4.0.17)(@cspell/dict-html-symbol-entities@4.0.3)(@cspell/dict-html@4.0.11)(@cspell/dict-typescript@3.2.2)
+      '@cspell/dict-monkeyc': 1.0.10
+      '@cspell/dict-node': 5.0.7
+      '@cspell/dict-npm': 5.2.9
+      '@cspell/dict-php': 4.0.14
+      '@cspell/dict-powershell': 5.0.14
+      '@cspell/dict-public-licenses': 2.0.13
+      '@cspell/dict-python': 4.2.18
+      '@cspell/dict-r': 2.1.0
+      '@cspell/dict-ruby': 5.0.8
+      '@cspell/dict-rust': 4.0.11
+      '@cspell/dict-scala': 5.0.7
+      '@cspell/dict-shell': 1.1.0
+      '@cspell/dict-software-terms': 5.1.2
+      '@cspell/dict-sql': 2.2.0
+      '@cspell/dict-svelte': 1.0.6
+      '@cspell/dict-swift': 2.0.5
+      '@cspell/dict-terraform': 1.1.1
+      '@cspell/dict-typescript': 3.2.2
+      '@cspell/dict-vue': 3.0.4
+
+  '@cspell/cspell-pipe@9.1.2': {}
+
+  '@cspell/cspell-resolver@9.1.2':
     dependencies:
-      '@types/conventional-commits-parser': 5.0.0
-      chalk: 5.3.0
+      global-directory: 4.0.1
 
-  '@cspotcode/source-map-support@0.8.1':
+  '@cspell/cspell-service-bus@9.1.2': {}
+
+  '@cspell/cspell-types@9.1.2': {}
+
+  '@cspell/dict-ada@4.1.0': {}
+
+  '@cspell/dict-al@1.1.0': {}
+
+  '@cspell/dict-aws@4.0.10': {}
+
+  '@cspell/dict-bash@4.2.0':
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.9
+      '@cspell/dict-shell': 1.1.0
 
-  '@dabh/diagnostics@2.0.3':
+  '@cspell/dict-companies@3.2.1': {}
+
+  '@cspell/dict-cpp@6.0.8': {}
+
+  '@cspell/dict-cryptocurrencies@5.0.4': {}
+
+  '@cspell/dict-csharp@4.0.6': {}
+
+  '@cspell/dict-css@4.0.17': {}
+
+  '@cspell/dict-dart@2.3.0': {}
+
+  '@cspell/dict-data-science@2.0.8': {}
+
+  '@cspell/dict-django@4.1.4': {}
+
+  '@cspell/dict-docker@1.1.14': {}
+
+  '@cspell/dict-dotnet@5.0.9': {}
+
+  '@cspell/dict-elixir@4.0.7': {}
+
+  '@cspell/dict-en-common-misspellings@2.1.2': {}
+
+  '@cspell/dict-en-gb-mit@3.1.3': {}
+
+  '@cspell/dict-en_us@4.4.13': {}
+
+  '@cspell/dict-filetypes@3.0.12': {}
+
+  '@cspell/dict-flutter@1.1.0': {}
+
+  '@cspell/dict-fonts@4.0.4': {}
+
+  '@cspell/dict-fsharp@1.1.0': {}
+
+  '@cspell/dict-fullstack@3.2.6': {}
+
+  '@cspell/dict-gaming-terms@1.1.1': {}
+
+  '@cspell/dict-git@3.0.6': {}
+
+  '@cspell/dict-golang@6.0.22': {}
+
+  '@cspell/dict-google@1.0.8': {}
+
+  '@cspell/dict-haskell@4.0.5': {}
+
+  '@cspell/dict-html-symbol-entities@4.0.3': {}
+
+  '@cspell/dict-html@4.0.11': {}
+
+  '@cspell/dict-java@5.0.11': {}
+
+  '@cspell/dict-julia@1.1.0': {}
+
+  '@cspell/dict-k8s@1.0.11': {}
+
+  '@cspell/dict-kotlin@1.1.0': {}
+
+  '@cspell/dict-latex@4.0.3': {}
+
+  '@cspell/dict-lorem-ipsum@4.0.4': {}
+
+  '@cspell/dict-lua@4.0.7': {}
+
+  '@cspell/dict-makefile@1.0.4': {}
+
+  '@cspell/dict-markdown@2.0.11(@cspell/dict-css@4.0.17)(@cspell/dict-html-symbol-entities@4.0.3)(@cspell/dict-html@4.0.11)(@cspell/dict-typescript@3.2.2)':
     dependencies:
-      colorspace: 1.1.4
-      enabled: 2.0.0
-      kuler: 2.0.0
+      '@cspell/dict-css': 4.0.17
+      '@cspell/dict-html': 4.0.11
+      '@cspell/dict-html-symbol-entities': 4.0.3
+      '@cspell/dict-typescript': 3.2.2
+
+  '@cspell/dict-monkeyc@1.0.10': {}
+
+  '@cspell/dict-node@5.0.7': {}
+
+  '@cspell/dict-npm@5.2.9': {}
+
+  '@cspell/dict-php@4.0.14': {}
+
+  '@cspell/dict-powershell@5.0.14': {}
+
+  '@cspell/dict-public-licenses@2.0.13': {}
 
-  '@es-joy/jsdoccomment@0.43.0':
+  '@cspell/dict-python@4.2.18':
     dependencies:
-      '@types/eslint': 8.56.10
-      '@types/estree': 1.0.5
-      '@typescript-eslint/types': 7.10.0
-      comment-parser: 1.4.1
-      esquery: 1.5.0
-      jsdoc-type-pratt-parser: 4.0.0
+      '@cspell/dict-data-science': 2.0.8
 
-  '@esbuild/aix-ppc64@0.20.2':
-    optional: true
+  '@cspell/dict-r@2.1.0': {}
 
-  '@esbuild/aix-ppc64@0.21.3':
-    optional: true
+  '@cspell/dict-ruby@5.0.8': {}
+
+  '@cspell/dict-rust@4.0.11': {}
+
+  '@cspell/dict-scala@5.0.7': {}
+
+  '@cspell/dict-shell@1.1.0': {}
+
+  '@cspell/dict-software-terms@5.1.2': {}
+
+  '@cspell/dict-sql@2.2.0': {}
+
+  '@cspell/dict-svelte@1.0.6': {}
+
+  '@cspell/dict-swift@2.0.5': {}
+
+  '@cspell/dict-terraform@1.1.1': {}
+
+  '@cspell/dict-typescript@3.2.2': {}
+
+  '@cspell/dict-vue@3.0.4': {}
+
+  '@cspell/dynamic-import@9.1.2':
+    dependencies:
+      '@cspell/url': 9.1.2
+      import-meta-resolve: 4.1.0
 
-  '@esbuild/android-arm64@0.20.2':
-    optional: true
+  '@cspell/eslint-plugin@9.1.2(eslint@9.30.1(jiti@2.4.2))':
+    dependencies:
+      '@cspell/cspell-types': 9.1.2
+      '@cspell/url': 9.1.2
+      cspell-lib: 9.1.2
+      eslint: 9.30.1(jiti@2.4.2)
+      synckit: 0.11.8
 
-  '@esbuild/android-arm64@0.21.3':
-    optional: true
+  '@cspell/filetypes@9.1.2': {}
 
-  '@esbuild/android-arm@0.20.2':
-    optional: true
+  '@cspell/strong-weak-map@9.1.2': {}
 
-  '@esbuild/android-arm@0.21.3':
-    optional: true
+  '@cspell/url@9.1.2': {}
 
-  '@esbuild/android-x64@0.20.2':
-    optional: true
+  '@cspotcode/source-map-support@0.8.1':
+    dependencies:
+      '@jridgewell/trace-mapping': 0.3.9
 
-  '@esbuild/android-x64@0.21.3':
-    optional: true
+  '@csstools/color-helpers@5.0.2': {}
 
-  '@esbuild/darwin-arm64@0.20.2':
-    optional: true
+  '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
 
-  '@esbuild/darwin-arm64@0.21.3':
-    optional: true
+  '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/color-helpers': 5.0.2
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
 
-  '@esbuild/darwin-x64@0.20.2':
-    optional: true
+  '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-tokenizer': 3.0.4
 
-  '@esbuild/darwin-x64@0.21.3':
-    optional: true
+  '@csstools/css-tokenizer@3.0.4': {}
 
-  '@esbuild/freebsd-arm64@0.20.2':
-    optional: true
+  '@dabh/diagnostics@2.0.3':
+    dependencies:
+      colorspace: 1.1.4
+      enabled: 2.0.0
+      kuler: 2.0.0
 
-  '@esbuild/freebsd-arm64@0.21.3':
+  '@emnapi/core@1.4.3':
+    dependencies:
+      '@emnapi/wasi-threads': 1.0.2
+      tslib: 2.8.1
     optional: true
 
-  '@esbuild/freebsd-x64@0.20.2':
+  '@emnapi/runtime@1.4.3':
+    dependencies:
+      tslib: 2.8.1
     optional: true
 
-  '@esbuild/freebsd-x64@0.21.3':
+  '@emnapi/wasi-threads@1.0.2':
+    dependencies:
+      tslib: 2.8.1
     optional: true
 
-  '@esbuild/linux-arm64@0.20.2':
-    optional: true
+  '@es-joy/jsdoccomment@0.52.0':
+    dependencies:
+      '@types/estree': 1.0.8
+      '@typescript-eslint/types': 8.35.1
+      comment-parser: 1.4.1
+      esquery: 1.6.0
+      jsdoc-type-pratt-parser: 4.1.0
 
-  '@esbuild/linux-arm64@0.21.3':
+  '@esbuild/aix-ppc64@0.25.5':
     optional: true
 
-  '@esbuild/linux-arm@0.20.2':
+  '@esbuild/android-arm64@0.25.5':
     optional: true
 
-  '@esbuild/linux-arm@0.21.3':
+  '@esbuild/android-arm@0.25.5':
     optional: true
 
-  '@esbuild/linux-ia32@0.20.2':
+  '@esbuild/android-x64@0.25.5':
     optional: true
 
-  '@esbuild/linux-ia32@0.21.3':
+  '@esbuild/darwin-arm64@0.25.5':
     optional: true
 
-  '@esbuild/linux-loong64@0.20.2':
+  '@esbuild/darwin-x64@0.25.5':
     optional: true
 
-  '@esbuild/linux-loong64@0.21.3':
+  '@esbuild/freebsd-arm64@0.25.5':
     optional: true
 
-  '@esbuild/linux-mips64el@0.20.2':
+  '@esbuild/freebsd-x64@0.25.5':
     optional: true
 
-  '@esbuild/linux-mips64el@0.21.3':
+  '@esbuild/linux-arm64@0.25.5':
     optional: true
 
-  '@esbuild/linux-ppc64@0.20.2':
+  '@esbuild/linux-arm@0.25.5':
     optional: true
 
-  '@esbuild/linux-ppc64@0.21.3':
+  '@esbuild/linux-ia32@0.25.5':
     optional: true
 
-  '@esbuild/linux-riscv64@0.20.2':
+  '@esbuild/linux-loong64@0.25.5':
     optional: true
 
-  '@esbuild/linux-riscv64@0.21.3':
+  '@esbuild/linux-mips64el@0.25.5':
     optional: true
 
-  '@esbuild/linux-s390x@0.20.2':
+  '@esbuild/linux-ppc64@0.25.5':
     optional: true
 
-  '@esbuild/linux-s390x@0.21.3':
+  '@esbuild/linux-riscv64@0.25.5':
     optional: true
 
-  '@esbuild/linux-x64@0.20.2':
+  '@esbuild/linux-s390x@0.25.5':
     optional: true
 
-  '@esbuild/linux-x64@0.21.3':
+  '@esbuild/linux-x64@0.25.5':
     optional: true
 
-  '@esbuild/netbsd-x64@0.20.2':
+  '@esbuild/netbsd-arm64@0.25.5':
     optional: true
 
-  '@esbuild/netbsd-x64@0.21.3':
+  '@esbuild/netbsd-x64@0.25.5':
     optional: true
 
-  '@esbuild/openbsd-x64@0.20.2':
+  '@esbuild/openbsd-arm64@0.25.5':
     optional: true
 
-  '@esbuild/openbsd-x64@0.21.3':
+  '@esbuild/openbsd-x64@0.25.5':
     optional: true
 
-  '@esbuild/sunos-x64@0.20.2':
+  '@esbuild/sunos-x64@0.25.5':
     optional: true
 
-  '@esbuild/sunos-x64@0.21.3':
+  '@esbuild/win32-arm64@0.25.5':
     optional: true
 
-  '@esbuild/win32-arm64@0.20.2':
+  '@esbuild/win32-ia32@0.25.5':
     optional: true
 
-  '@esbuild/win32-arm64@0.21.3':
+  '@esbuild/win32-x64@0.25.5':
     optional: true
 
-  '@esbuild/win32-ia32@0.20.2':
-    optional: true
+  '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))':
+    dependencies:
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-visitor-keys: 3.4.3
 
-  '@esbuild/win32-ia32@0.21.3':
-    optional: true
+  '@eslint-community/regexpp@4.12.1': {}
 
-  '@esbuild/win32-x64@0.20.2':
-    optional: true
+  '@eslint/config-array@0.21.0':
+    dependencies:
+      '@eslint/object-schema': 2.1.6
+      debug: 4.4.1
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
 
-  '@esbuild/win32-x64@0.21.3':
-    optional: true
+  '@eslint/config-helpers@0.3.0': {}
 
-  '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
+  '@eslint/core@0.14.0':
     dependencies:
-      eslint: 8.57.0
-      eslint-visitor-keys: 3.4.3
+      '@types/json-schema': 7.0.15
 
-  '@eslint-community/regexpp@4.10.0': {}
+  '@eslint/core@0.15.1':
+    dependencies:
+      '@types/json-schema': 7.0.15
 
-  '@eslint/eslintrc@2.1.4':
+  '@eslint/eslintrc@3.3.1':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4
-      espree: 9.6.1
-      globals: 13.24.0
-      ignore: 5.3.1
-      import-fresh: 3.3.0
+      debug: 4.4.1
+      espree: 10.4.0
+      globals: 14.0.0
+      ignore: 5.3.2
+      import-fresh: 3.3.1
       js-yaml: 4.1.0
       minimatch: 3.1.2
       strip-json-comments: 3.1.1
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@8.57.0': {}
+  '@eslint/js@9.30.1': {}
+
+  '@eslint/object-schema@2.1.6': {}
+
+  '@eslint/plugin-kit@0.3.3':
+    dependencies:
+      '@eslint/core': 0.15.1
+      levn: 0.4.1
 
   '@gar/promisify@1.1.3':
     optional: true
 
-  '@humanwhocodes/config-array@0.11.14':
+  '@humanfs/core@0.19.1': {}
+
+  '@humanfs/node@0.16.6':
     dependencies:
-      '@humanwhocodes/object-schema': 2.0.3
-      debug: 4.3.4
-      minimatch: 3.1.2
-    transitivePeerDependencies:
-      - supports-color
+      '@humanfs/core': 0.19.1
+      '@humanwhocodes/retry': 0.3.1
+
+  '@humanwhocodes/gitignore-to-minimatch@1.0.2': {}
 
   '@humanwhocodes/module-importer@1.0.1': {}
 
-  '@humanwhocodes/object-schema@2.0.3': {}
+  '@humanwhocodes/retry@0.3.1': {}
 
-  '@iarna/toml@2.2.5': {}
+  '@humanwhocodes/retry@0.4.3': {}
 
-  '@inquirer/figures@1.0.2': {}
+  '@isaacs/balanced-match@4.0.1': {}
+
+  '@isaacs/brace-expansion@5.0.0':
+    dependencies:
+      '@isaacs/balanced-match': 4.0.1
 
   '@isaacs/cliui@8.0.2':
     dependencies:
@@ -6962,78 +7035,58 @@ snapshots:
 
   '@isaacs/fs-minipass@4.0.1':
     dependencies:
-      minipass: 7.1.1
+      minipass: 7.1.2
 
   '@istanbuljs/schema@0.1.3': {}
 
   '@jercle/yargonaut@1.1.5':
     dependencies:
       chalk: 4.1.2
-      figlet: 1.7.0
+      figlet: 1.8.1
       parent-require: 1.0.0
 
-  '@jest/expect-utils@29.7.0':
-    dependencies:
-      jest-get-type: 29.6.3
-
-  '@jest/schemas@29.6.3':
-    dependencies:
-      '@sinclair/typebox': 0.27.8
-
-  '@jest/types@29.6.3':
-    dependencies:
-      '@jest/schemas': 29.6.3
-      '@types/istanbul-lib-coverage': 2.0.6
-      '@types/istanbul-reports': 3.0.4
-      '@types/node': 20.12.12
-      '@types/yargs': 17.0.32
-      chalk: 4.1.2
-
-  '@jridgewell/gen-mapping@0.3.5':
+  '@jridgewell/gen-mapping@0.3.12':
     dependencies:
-      '@jridgewell/set-array': 1.2.1
-      '@jridgewell/sourcemap-codec': 1.4.15
-      '@jridgewell/trace-mapping': 0.3.25
+      '@jridgewell/sourcemap-codec': 1.5.4
+      '@jridgewell/trace-mapping': 0.3.29
 
   '@jridgewell/resolve-uri@3.1.2': {}
 
-  '@jridgewell/set-array@1.2.1': {}
+  '@jridgewell/sourcemap-codec@1.5.4': {}
 
-  '@jridgewell/sourcemap-codec@1.4.15': {}
-
-  '@jridgewell/trace-mapping@0.3.25':
+  '@jridgewell/trace-mapping@0.3.29':
     dependencies:
       '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.4
 
   '@jridgewell/trace-mapping@0.3.9':
     dependencies:
       '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.4
 
-  '@ljharb/through@2.3.13':
+  '@jsr/std__assert@1.0.13':
     dependencies:
-      call-bind: 1.0.7
+      '@jsr/std__internal': 1.0.9
 
-  '@microsoft/tsdoc-config@0.16.2':
+  '@jsr/std__expect@1.0.16':
     dependencies:
-      '@microsoft/tsdoc': 0.14.2
-      ajv: 6.12.6
-      jju: 1.4.0
-      resolve: 1.19.0
+      '@jsr/std__assert': 1.0.13
+      '@jsr/std__internal': 1.0.9
 
-  '@microsoft/tsdoc@0.14.2': {}
+  '@jsr/std__internal@1.0.9': {}
 
-  '@mikro-orm/cli@6.2.7':
+  '@mikro-orm/cli@6.4.16(mariadb@3.4.2)':
     dependencies:
       '@jercle/yargonaut': 1.1.5
-      '@mikro-orm/core': 6.2.7
-      '@mikro-orm/knex': 6.2.7(@mikro-orm/core@6.2.7)(sqlite3@5.1.7)
-      fs-extra: 11.2.0
+      '@mikro-orm/core': 6.4.16
+      '@mikro-orm/knex': 6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)(sqlite3@5.1.7)
+      fs-extra: 11.3.0
       tsconfig-paths: 4.2.0
       yargs: 17.7.2
     transitivePeerDependencies:
       - better-sqlite3
+      - libsql
+      - mariadb
       - mysql
       - mysql2
       - pg
@@ -7042,24 +7095,25 @@ snapshots:
       - supports-color
       - tedious
 
-  '@mikro-orm/core@6.2.7':
+  '@mikro-orm/core@6.4.16':
     dependencies:
-      dataloader: 2.2.2
-      dotenv: 16.4.5
+      dataloader: 2.2.3
+      dotenv: 16.5.0
       esprima: 4.0.1
-      fs-extra: 11.2.0
+      fs-extra: 11.3.0
       globby: 11.1.0
-      mikro-orm: 6.2.7
+      mikro-orm: 6.4.16
       reflect-metadata: 0.2.2
 
-  '@mikro-orm/knex@6.2.7(@mikro-orm/core@6.2.7)(sqlite3@5.1.7)':
+  '@mikro-orm/knex@6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)(sqlite3@5.1.7)':
     dependencies:
-      '@mikro-orm/core': 6.2.7
-      fs-extra: 11.2.0
+      '@mikro-orm/core': 6.4.16
+      fs-extra: 11.3.0
       knex: 3.1.0(sqlite3@5.1.7)
       sqlstring: 2.3.3
+    optionalDependencies:
+      mariadb: 3.4.2
     transitivePeerDependencies:
-      - better-sqlite3
       - mysql
       - mysql2
       - pg
@@ -7068,13 +7122,14 @@ snapshots:
       - supports-color
       - tedious
 
-  '@mikro-orm/mariadb@6.2.7(@mikro-orm/core@6.2.7)':
+  '@mikro-orm/mariadb@6.4.16(@mikro-orm/core@6.4.16)':
     dependencies:
-      '@mikro-orm/core': 6.2.7
-      '@mikro-orm/knex': 6.2.7(@mikro-orm/core@6.2.7)(sqlite3@5.1.7)
-      mariadb: 3.3.0
+      '@mikro-orm/core': 6.4.16
+      '@mikro-orm/knex': 6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)(sqlite3@5.1.7)
+      mariadb: 3.4.2
     transitivePeerDependencies:
       - better-sqlite3
+      - libsql
       - mysql
       - mysql2
       - pg
@@ -7083,22 +7138,24 @@ snapshots:
       - supports-color
       - tedious
 
-  '@mikro-orm/reflection@6.2.7(@mikro-orm/core@6.2.7)':
+  '@mikro-orm/reflection@6.4.16(@mikro-orm/core@6.4.16)':
     dependencies:
-      '@mikro-orm/core': 6.2.7
+      '@mikro-orm/core': 6.4.16
       globby: 11.1.0
-      ts-morph: 22.0.0
+      ts-morph: 26.0.0
 
-  '@mikro-orm/sqlite@6.2.7(@mikro-orm/core@6.2.7)':
+  '@mikro-orm/sqlite@6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)':
     dependencies:
-      '@mikro-orm/core': 6.2.7
-      '@mikro-orm/knex': 6.2.7(@mikro-orm/core@6.2.7)(sqlite3@5.1.7)
-      fs-extra: 11.2.0
+      '@mikro-orm/core': 6.4.16
+      '@mikro-orm/knex': 6.4.16(@mikro-orm/core@6.4.16)(mariadb@3.4.2)(sqlite3@5.1.7)
+      fs-extra: 11.3.0
       sqlite3: 5.1.7
       sqlstring-sqlite: 0.1.1
     transitivePeerDependencies:
       - better-sqlite3
       - bluebird
+      - libsql
+      - mariadb
       - mysql
       - mysql2
       - pg
@@ -7106,10 +7163,17 @@ snapshots:
       - supports-color
       - tedious
 
-  '@mongodb-js/saslprep@1.1.7':
+  '@mongodb-js/saslprep@1.3.0':
     dependencies:
       sparse-bitfield: 3.0.3
 
+  '@napi-rs/wasm-runtime@0.2.11':
+    dependencies:
+      '@emnapi/core': 1.4.3
+      '@emnapi/runtime': 1.4.3
+      '@tybys/wasm-util': 0.9.0
+    optional: true
+
   '@nearform/heap-profiler@2.0.0':
     dependencies:
       abort-controller: 3.0.0
@@ -7125,12 +7189,14 @@ snapshots:
   '@nodelib/fs.walk@1.2.8':
     dependencies:
       '@nodelib/fs.scandir': 2.1.5
-      fastq: 1.17.1
+      fastq: 1.19.1
+
+  '@nolyfill/is-core-module@1.0.39': {}
 
   '@npmcli/fs@1.1.1':
     dependencies:
       '@gar/promisify': 1.1.3
-      semver: 7.6.2
+      semver: 7.7.2
     optional: true
 
   '@npmcli/move-file@1.1.2':
@@ -7139,154 +7205,90 @@ snapshots:
       rimraf: 3.0.2
     optional: true
 
-  '@octokit/auth-token@4.0.0': {}
-
-  '@octokit/core@5.2.0':
-    dependencies:
-      '@octokit/auth-token': 4.0.0
-      '@octokit/graphql': 7.1.0
-      '@octokit/request': 8.4.0
-      '@octokit/request-error': 5.1.0
-      '@octokit/types': 13.5.0
-      before-after-hook: 2.2.3
-      universal-user-agent: 6.0.1
-
-  '@octokit/endpoint@9.0.5':
-    dependencies:
-      '@octokit/types': 13.5.0
-      universal-user-agent: 6.0.1
-
-  '@octokit/graphql@7.1.0':
-    dependencies:
-      '@octokit/request': 8.4.0
-      '@octokit/types': 13.5.0
-      universal-user-agent: 6.0.1
-
-  '@octokit/openapi-types@22.2.0': {}
-
-  '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)':
-    dependencies:
-      '@octokit/core': 5.2.0
-      '@octokit/types': 13.5.0
-
-  '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)':
-    dependencies:
-      '@octokit/core': 5.2.0
-
-  '@octokit/plugin-rest-endpoint-methods@13.2.2(@octokit/core@5.2.0)':
-    dependencies:
-      '@octokit/core': 5.2.0
-      '@octokit/types': 13.5.0
-
-  '@octokit/request-error@5.1.0':
-    dependencies:
-      '@octokit/types': 13.5.0
-      deprecation: 2.3.1
-      once: 1.4.0
-
-  '@octokit/request@8.4.0':
-    dependencies:
-      '@octokit/endpoint': 9.0.5
-      '@octokit/request-error': 5.1.0
-      '@octokit/types': 13.5.0
-      universal-user-agent: 6.0.1
-
-  '@octokit/rest@20.1.1':
-    dependencies:
-      '@octokit/core': 5.2.0
-      '@octokit/plugin-paginate-rest': 11.3.1(@octokit/core@5.2.0)
-      '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.0)
-      '@octokit/plugin-rest-endpoint-methods': 13.2.2(@octokit/core@5.2.0)
-
-  '@octokit/types@13.5.0':
-    dependencies:
-      '@octokit/openapi-types': 22.2.0
-
   '@one-ini/wasm@0.1.1': {}
 
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
-  '@pkgr/core@0.1.1': {}
+  '@pkgr/core@0.2.7': {}
 
-  '@pnpm/config.env-replace@1.1.0': {}
+  '@rolldown/pluginutils@1.0.0-beta.19': {}
 
-  '@pnpm/network.ca-file@1.0.2':
-    dependencies:
-      graceful-fs: 4.2.10
+  '@rolldown/pluginutils@1.0.0-beta.23': {}
 
-  '@pnpm/npm-conf@2.2.2':
-    dependencies:
-      '@pnpm/config.env-replace': 1.1.0
-      '@pnpm/network.ca-file': 1.0.2
-      config-chain: 1.1.13
+  '@rollup/rollup-android-arm-eabi@4.44.1':
+    optional: true
 
-  '@release-it/bumper@6.0.1(release-it@17.3.0(typescript@5.4.5))':
-    dependencies:
-      '@iarna/toml': 2.2.5
-      detect-indent: 7.0.1
-      fast-glob: 3.3.2
-      ini: 4.1.2
-      js-yaml: 4.1.0
-      lodash-es: 4.17.21
-      release-it: 17.3.0(typescript@5.4.5)
-      semver: 7.6.2
+  '@rollup/rollup-android-arm64@4.44.1':
+    optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.17.2':
+  '@rollup/rollup-darwin-arm64@4.44.1':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.17.2':
+  '@rollup/rollup-darwin-x64@4.44.1':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.17.2':
+  '@rollup/rollup-freebsd-arm64@4.44.1':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.17.2':
+  '@rollup/rollup-freebsd-x64@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
+  '@rollup/rollup-linux-arm-gnueabihf@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.17.2':
+  '@rollup/rollup-linux-arm-musleabihf@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.17.2':
+  '@rollup/rollup-linux-arm64-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.17.2':
+  '@rollup/rollup-linux-arm64-musl@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
+  '@rollup/rollup-linux-loongarch64-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.17.2':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.17.2':
+  '@rollup/rollup-linux-riscv64-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.17.2':
+  '@rollup/rollup-linux-riscv64-musl@4.44.1':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.17.2':
+  '@rollup/rollup-linux-s390x-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.17.2':
+  '@rollup/rollup-linux-x64-gnu@4.44.1':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.17.2':
+  '@rollup/rollup-linux-x64-musl@4.44.1':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.17.2':
+  '@rollup/rollup-win32-arm64-msvc@4.44.1':
     optional: true
 
-  '@rushstack/eslint-patch@1.10.3': {}
+  '@rollup/rollup-win32-ia32-msvc@4.44.1':
+    optional: true
 
-  '@sinclair/typebox@0.27.8': {}
+  '@rollup/rollup-win32-x64-msvc@4.44.1':
+    optional: true
 
   '@sindresorhus/is@5.6.0': {}
 
-  '@sindresorhus/merge-streams@2.3.0': {}
+  '@stylistic/eslint-plugin@2.11.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-visitor-keys: 4.2.1
+      espree: 10.4.0
+      estraverse: 5.3.0
+      picomatch: 4.0.2
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
 
   '@szmarczak/http-timer@5.0.1':
     dependencies:
@@ -7314,13 +7316,10 @@ snapshots:
   '@tootallnate/once@1.1.2':
     optional: true
 
-  '@tootallnate/quickjs-emscripten@0.23.0': {}
-
-  '@ts-morph/common@0.23.0':
+  '@ts-morph/common@0.27.0':
     dependencies:
-      fast-glob: 3.3.2
-      minimatch: 9.0.4
-      mkdirp: 3.0.1
+      fast-glob: 3.3.3
+      minimatch: 10.0.3
       path-browserify: 1.0.1
 
   '@tsconfig/node10@1.0.11': {}
@@ -7331,56 +7330,54 @@ snapshots:
 
   '@tsconfig/node16@1.0.4': {}
 
-  '@tsconfig/node20@20.1.4': {}
+  '@tsconfig/node22@22.0.2': {}
+
+  '@tybys/wasm-util@0.9.0':
+    dependencies:
+      tslib: 2.8.1
+    optional: true
 
-  '@types/conventional-commits-parser@5.0.0':
+  '@types/chai@5.2.2':
     dependencies:
-      '@types/node': 20.12.12
+      '@types/deep-eql': 4.0.2
 
-  '@types/eslint@8.56.10':
+  '@types/conventional-commits-parser@5.0.1':
     dependencies:
-      '@types/estree': 1.0.5
-      '@types/json-schema': 7.0.15
+      '@types/node': 24.0.10
+
+  '@types/deep-eql@4.0.2': {}
 
-  '@types/estree@1.0.5': {}
+  '@types/estree@1.0.8': {}
 
-  '@types/geojson@7946.0.14': {}
+  '@types/geojson@7946.0.16': {}
 
   '@types/http-cache-semantics@4.0.4': {}
 
   '@types/istanbul-lib-coverage@2.0.6': {}
 
-  '@types/istanbul-lib-report@3.0.3':
-    dependencies:
-      '@types/istanbul-lib-coverage': 2.0.6
-
-  '@types/istanbul-reports@3.0.4':
-    dependencies:
-      '@types/istanbul-lib-report': 3.0.3
-
-  '@types/jsdom@21.1.6':
+  '@types/jsdom@21.1.7':
     dependencies:
-      '@types/node': 20.12.12
+      '@types/node': 24.0.10
       '@types/tough-cookie': 4.0.5
-      parse5: 7.1.2
+      parse5: 7.3.0
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/json5@0.0.29': {}
-
   '@types/long@4.0.2': {}
 
-  '@types/node@20.12.12':
+  '@types/node@22.16.0':
+    dependencies:
+      undici-types: 6.21.0
+
+  '@types/node@24.0.10':
     dependencies:
-      undici-types: 5.26.5
+      undici-types: 7.8.0
 
   '@types/offscreencanvas@2019.3.0': {}
 
   '@types/seedrandom@2.4.34': {}
 
-  '@types/semver@7.5.8': {}
-
-  '@types/stack-utils@2.0.3': {}
+  '@types/semver@7.7.0': {}
 
   '@types/tough-cookie@4.0.5': {}
 
@@ -7394,273 +7391,333 @@ snapshots:
     dependencies:
       '@types/webidl-conversions': 7.0.3
 
-  '@types/ws@8.5.10':
-    dependencies:
-      '@types/node': 20.12.12
-
-  '@types/yargs-parser@21.0.3': {}
-
-  '@types/yargs@17.0.32':
+  '@types/ws@8.18.1':
     dependencies:
-      '@types/yargs-parser': 21.0.3
+      '@types/node': 24.0.10
 
-  '@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
+  '@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)':
     dependencies:
-      '@eslint-community/regexpp': 4.10.0
-      '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/scope-manager': 7.10.0
-      '@typescript-eslint/type-utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/visitor-keys': 7.10.0
-      eslint: 8.57.0
+      '@eslint-community/regexpp': 4.12.1
+      '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      '@typescript-eslint/scope-manager': 8.35.1
+      '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      '@typescript-eslint/visitor-keys': 8.35.1
+      eslint: 9.30.1(jiti@2.4.2)
       graphemer: 1.4.0
-      ignore: 5.3.1
+      ignore: 7.0.5
       natural-compare: 1.4.0
-      ts-api-utils: 1.3.0(typescript@5.4.5)
-    optionalDependencies:
-      typescript: 5.4.5
+      ts-api-utils: 2.1.0(typescript@5.8.3)
+      typescript: 5.8.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5)':
+  '@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)':
     dependencies:
-      '@typescript-eslint/scope-manager': 7.10.0
-      '@typescript-eslint/types': 7.10.0
-      '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5)
-      '@typescript-eslint/visitor-keys': 7.10.0
-      debug: 4.3.4
-      eslint: 8.57.0
-    optionalDependencies:
-      typescript: 5.4.5
+      '@typescript-eslint/scope-manager': 8.35.1
+      '@typescript-eslint/types': 8.35.1
+      '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3)
+      '@typescript-eslint/visitor-keys': 8.35.1
+      debug: 4.4.1
+      eslint: 9.30.1(jiti@2.4.2)
+      typescript: 5.8.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@7.10.0':
+  '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)':
     dependencies:
-      '@typescript-eslint/types': 7.10.0
-      '@typescript-eslint/visitor-keys': 7.10.0
+      '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3)
+      '@typescript-eslint/types': 8.35.1
+      debug: 4.4.1
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
 
-  '@typescript-eslint/type-utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)':
+  '@typescript-eslint/scope-manager@8.35.1':
     dependencies:
-      '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5)
-      '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      debug: 4.3.4
-      eslint: 8.57.0
-      ts-api-utils: 1.3.0(typescript@5.4.5)
-    optionalDependencies:
-      typescript: 5.4.5
+      '@typescript-eslint/types': 8.35.1
+      '@typescript-eslint/visitor-keys': 8.35.1
+
+  '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)':
+    dependencies:
+      typescript: 5.8.3
+
+  '@typescript-eslint/type-utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)':
+    dependencies:
+      '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      debug: 4.4.1
+      eslint: 9.30.1(jiti@2.4.2)
+      ts-api-utils: 2.1.0(typescript@5.8.3)
+      typescript: 5.8.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@7.10.0': {}
+  '@typescript-eslint/types@8.35.1': {}
 
-  '@typescript-eslint/typescript-estree@7.10.0(typescript@5.4.5)':
+  '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)':
     dependencies:
-      '@typescript-eslint/types': 7.10.0
-      '@typescript-eslint/visitor-keys': 7.10.0
-      debug: 4.3.4
-      globby: 11.1.0
+      '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3)
+      '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3)
+      '@typescript-eslint/types': 8.35.1
+      '@typescript-eslint/visitor-keys': 8.35.1
+      debug: 4.4.1
+      fast-glob: 3.3.3
       is-glob: 4.0.3
-      minimatch: 9.0.4
-      semver: 7.6.2
-      ts-api-utils: 1.3.0(typescript@5.4.5)
-    optionalDependencies:
-      typescript: 5.4.5
+      minimatch: 9.0.5
+      semver: 7.7.2
+      ts-api-utils: 2.1.0(typescript@5.8.3)
+      typescript: 5.8.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)':
+  '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
-      '@typescript-eslint/scope-manager': 7.10.0
-      '@typescript-eslint/types': 7.10.0
-      '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5)
-      eslint: 8.57.0
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      '@typescript-eslint/scope-manager': 8.35.1
+      '@typescript-eslint/types': 8.35.1
+      '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3)
+      eslint: 9.30.1(jiti@2.4.2)
+      typescript: 5.8.3
     transitivePeerDependencies:
       - supports-color
-      - typescript
 
-  '@typescript-eslint/visitor-keys@7.10.0':
+  '@typescript-eslint/visitor-keys@8.35.1':
     dependencies:
-      '@typescript-eslint/types': 7.10.0
-      eslint-visitor-keys: 3.4.3
+      '@typescript-eslint/types': 8.35.1
+      eslint-visitor-keys: 4.2.1
+
+  '@unrs/resolver-binding-android-arm-eabi@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-android-arm64@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-darwin-arm64@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-darwin-x64@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-freebsd-x64@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-arm-gnueabihf@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-arm-musleabihf@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-arm64-gnu@1.10.1':
+    optional: true
 
-  '@ungap/structured-clone@1.2.0': {}
+  '@unrs/resolver-binding-linux-arm64-musl@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-ppc64-gnu@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-riscv64-gnu@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-riscv64-musl@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-s390x-gnu@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-x64-gnu@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-linux-x64-musl@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-wasm32-wasi@1.10.1':
+    dependencies:
+      '@napi-rs/wasm-runtime': 0.2.11
+    optional: true
 
-  '@vitejs/plugin-vue-jsx@3.1.0(vite@5.2.11(@types/node@20.12.12))(vue@3.4.27(typescript@5.4.5))':
+  '@unrs/resolver-binding-win32-arm64-msvc@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-win32-ia32-msvc@1.10.1':
+    optional: true
+
+  '@unrs/resolver-binding-win32-x64-msvc@1.10.1':
+    optional: true
+
+  '@vitejs/plugin-vue-jsx@5.0.1(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
     dependencies:
-      '@babel/core': 7.24.5
-      '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.24.5)
-      '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.5)
-      vite: 5.2.11(@types/node@20.12.12)
-      vue: 3.4.27(typescript@5.4.5)
+      '@babel/core': 7.28.0
+      '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
+      '@rolldown/pluginutils': 1.0.0-beta.23
+      '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.28.0)
+      vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
+      vue: 3.5.17(typescript@5.8.3)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitejs/plugin-vue@5.0.4(vite@5.2.11(@types/node@20.12.12))(vue@3.4.27(typescript@5.4.5))':
+  '@vitejs/plugin-vue@6.0.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))':
     dependencies:
-      vite: 5.2.11(@types/node@20.12.12)
-      vue: 3.4.27(typescript@5.4.5)
+      '@rolldown/pluginutils': 1.0.0-beta.19
+      vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
+      vue: 3.5.17(typescript@5.8.3)
 
-  '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.12.12)(jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)))':
+  '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(tsx@4.20.3)(yaml@2.8.0))':
     dependencies:
       '@ampproject/remapping': 2.3.0
-      '@bcoe/v8-coverage': 0.2.3
-      debug: 4.3.4
+      '@bcoe/v8-coverage': 1.0.2
+      ast-v8-to-istanbul: 0.3.3
+      debug: 4.4.1
       istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
-      istanbul-lib-source-maps: 5.0.4
+      istanbul-lib-source-maps: 5.0.6
       istanbul-reports: 3.1.7
-      magic-string: 0.30.10
-      magicast: 0.3.4
-      picocolors: 1.0.1
-      std-env: 3.7.0
-      strip-literal: 2.1.0
-      test-exclude: 6.0.0
-      vitest: 1.6.0(@types/node@20.12.12)(jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      magic-string: 0.30.17
+      magicast: 0.3.5
+      std-env: 3.9.0
+      test-exclude: 7.0.1
+      tinyrainbow: 2.0.0
+      vitest: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(tsx@4.20.3)(yaml@2.8.0)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitest/expect@1.6.0':
+  '@vitest/expect@3.2.4':
+    dependencies:
+      '@types/chai': 5.2.2
+      '@vitest/spy': 3.2.4
+      '@vitest/utils': 3.2.4
+      chai: 5.2.0
+      tinyrainbow: 2.0.0
+
+  '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))':
     dependencies:
-      '@vitest/spy': 1.6.0
-      '@vitest/utils': 1.6.0
-      chai: 4.4.1
+      '@vitest/spy': 3.2.4
+      estree-walker: 3.0.3
+      magic-string: 0.30.17
+    optionalDependencies:
+      vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
 
-  '@vitest/runner@1.6.0':
+  '@vitest/pretty-format@3.2.4':
     dependencies:
-      '@vitest/utils': 1.6.0
-      p-limit: 5.0.0
-      pathe: 1.1.2
+      tinyrainbow: 2.0.0
 
-  '@vitest/snapshot@1.6.0':
+  '@vitest/runner@3.2.4':
     dependencies:
-      magic-string: 0.30.10
-      pathe: 1.1.2
-      pretty-format: 29.7.0
+      '@vitest/utils': 3.2.4
+      pathe: 2.0.3
+      strip-literal: 3.0.0
 
-  '@vitest/spy@1.6.0':
+  '@vitest/snapshot@3.2.4':
     dependencies:
-      tinyspy: 2.2.1
+      '@vitest/pretty-format': 3.2.4
+      magic-string: 0.30.17
+      pathe: 2.0.3
 
-  '@vitest/utils@1.6.0':
+  '@vitest/spy@3.2.4':
     dependencies:
-      diff-sequences: 29.6.3
-      estree-walker: 3.0.3
-      loupe: 2.3.7
-      pretty-format: 29.7.0
+      tinyspy: 4.0.3
 
-  '@vue/babel-helper-vue-transform-on@1.2.2': {}
+  '@vitest/utils@3.2.4':
+    dependencies:
+      '@vitest/pretty-format': 3.2.4
+      loupe: 3.1.4
+      tinyrainbow: 2.0.0
+
+  '@vue/babel-helper-vue-transform-on@1.4.0': {}
 
-  '@vue/babel-plugin-jsx@1.2.2(@babel/core@7.24.5)':
+  '@vue/babel-plugin-jsx@1.4.0(@babel/core@7.28.0)':
     dependencies:
-      '@babel/helper-module-imports': 7.22.15
-      '@babel/helper-plugin-utils': 7.24.5
-      '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.5)
-      '@babel/template': 7.24.0
-      '@babel/traverse': 7.24.5
-      '@babel/types': 7.24.5
-      '@vue/babel-helper-vue-transform-on': 1.2.2
-      '@vue/babel-plugin-resolve-type': 1.2.2(@babel/core@7.24.5)
-      camelcase: 6.3.0
-      html-tags: 3.3.1
-      svg-tags: 1.0.0
+      '@babel/helper-module-imports': 7.27.1
+      '@babel/helper-plugin-utils': 7.27.1
+      '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
+      '@babel/template': 7.27.2
+      '@babel/traverse': 7.28.0
+      '@babel/types': 7.28.0
+      '@vue/babel-helper-vue-transform-on': 1.4.0
+      '@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.28.0)
+      '@vue/shared': 3.5.17
     optionalDependencies:
-      '@babel/core': 7.24.5
+      '@babel/core': 7.28.0
     transitivePeerDependencies:
       - supports-color
 
-  '@vue/babel-plugin-resolve-type@1.2.2(@babel/core@7.24.5)':
+  '@vue/babel-plugin-resolve-type@1.4.0(@babel/core@7.28.0)':
     dependencies:
-      '@babel/code-frame': 7.24.2
-      '@babel/core': 7.24.5
-      '@babel/helper-module-imports': 7.22.15
-      '@babel/helper-plugin-utils': 7.24.5
-      '@babel/parser': 7.24.5
-      '@vue/compiler-sfc': 3.4.27
+      '@babel/code-frame': 7.27.1
+      '@babel/core': 7.28.0
+      '@babel/helper-module-imports': 7.27.1
+      '@babel/helper-plugin-utils': 7.27.1
+      '@babel/parser': 7.28.0
+      '@vue/compiler-sfc': 3.5.17
+    transitivePeerDependencies:
+      - supports-color
 
-  '@vue/compiler-core@3.4.27':
+  '@vue/compiler-core@3.5.17':
     dependencies:
-      '@babel/parser': 7.24.5
-      '@vue/shared': 3.4.27
+      '@babel/parser': 7.28.0
+      '@vue/shared': 3.5.17
       entities: 4.5.0
       estree-walker: 2.0.2
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
-  '@vue/compiler-dom@3.4.27':
+  '@vue/compiler-dom@3.5.17':
     dependencies:
-      '@vue/compiler-core': 3.4.27
-      '@vue/shared': 3.4.27
+      '@vue/compiler-core': 3.5.17
+      '@vue/shared': 3.5.17
 
-  '@vue/compiler-sfc@3.4.27':
+  '@vue/compiler-sfc@3.5.17':
     dependencies:
-      '@babel/parser': 7.24.5
-      '@vue/compiler-core': 3.4.27
-      '@vue/compiler-dom': 3.4.27
-      '@vue/compiler-ssr': 3.4.27
-      '@vue/shared': 3.4.27
+      '@babel/parser': 7.28.0
+      '@vue/compiler-core': 3.5.17
+      '@vue/compiler-dom': 3.5.17
+      '@vue/compiler-ssr': 3.5.17
+      '@vue/shared': 3.5.17
       estree-walker: 2.0.2
-      magic-string: 0.30.10
-      postcss: 8.4.38
-      source-map-js: 1.2.0
-
-  '@vue/compiler-ssr@3.4.27':
-    dependencies:
-      '@vue/compiler-dom': 3.4.27
-      '@vue/shared': 3.4.27
-
-  '@vue/devtools-api@6.6.1': {}
+      magic-string: 0.30.17
+      postcss: 8.5.6
+      source-map-js: 1.2.1
 
-  '@vue/eslint-config-prettier@9.0.0(@types/eslint@8.56.10)(eslint@8.57.0)(prettier@3.2.5)':
+  '@vue/compiler-ssr@3.5.17':
     dependencies:
-      eslint: 8.57.0
-      eslint-config-prettier: 9.1.0(eslint@8.57.0)
-      eslint-plugin-prettier: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5)
-      prettier: 3.2.5
-    transitivePeerDependencies:
-      - '@types/eslint'
+      '@vue/compiler-dom': 3.5.17
+      '@vue/shared': 3.5.17
 
-  '@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.26.0(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5)':
-    dependencies:
-      '@typescript-eslint/eslint-plugin': 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      eslint: 8.57.0
-      eslint-plugin-vue: 9.26.0(eslint@8.57.0)
-      vue-eslint-parser: 9.4.2(eslint@8.57.0)
-    optionalDependencies:
-      typescript: 5.4.5
-    transitivePeerDependencies:
-      - supports-color
+  '@vue/devtools-api@6.6.4': {}
 
-  '@vue/reactivity@3.4.27':
+  '@vue/reactivity@3.5.17':
     dependencies:
-      '@vue/shared': 3.4.27
+      '@vue/shared': 3.5.17
 
-  '@vue/runtime-core@3.4.27':
+  '@vue/runtime-core@3.5.17':
     dependencies:
-      '@vue/reactivity': 3.4.27
-      '@vue/shared': 3.4.27
+      '@vue/reactivity': 3.5.17
+      '@vue/shared': 3.5.17
 
-  '@vue/runtime-dom@3.4.27':
+  '@vue/runtime-dom@3.5.17':
     dependencies:
-      '@vue/runtime-core': 3.4.27
-      '@vue/shared': 3.4.27
+      '@vue/reactivity': 3.5.17
+      '@vue/runtime-core': 3.5.17
+      '@vue/shared': 3.5.17
       csstype: 3.1.3
 
-  '@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.4.5))':
+  '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))':
     dependencies:
-      '@vue/compiler-ssr': 3.4.27
-      '@vue/shared': 3.4.27
-      vue: 3.4.27(typescript@5.4.5)
+      '@vue/compiler-ssr': 3.5.17
+      '@vue/shared': 3.5.17
+      vue: 3.5.17(typescript@5.8.3)
 
-  '@vue/shared@3.4.27': {}
+  '@vue/shared@3.5.17': {}
 
   '@vue/test-utils@2.4.6':
     dependencies:
-      js-beautify: 1.15.1
-      vue-component-type-helpers: 2.0.19
+      js-beautify: 1.15.4
+      vue-component-type-helpers: 2.2.12
 
-  '@vue/tsconfig@0.5.1': {}
+  '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))':
+    optionalDependencies:
+      typescript: 5.8.3
+      vue: 3.5.17(typescript@5.8.3)
 
   '@webgpu/types@0.1.16': {}
 
@@ -7678,9 +7735,9 @@ snapshots:
     dependencies:
       event-target-shim: 5.0.1
 
-  acorn-jsx@5.3.2(acorn@8.11.3):
+  acorn-jsx@5.3.2(acorn@8.15.0):
     dependencies:
-      acorn: 8.11.3
+      acorn: 8.15.0
 
   acorn-node@1.8.2:
     dependencies:
@@ -7690,26 +7747,24 @@ snapshots:
 
   acorn-walk@7.2.0: {}
 
-  acorn-walk@8.3.2: {}
+  acorn-walk@8.3.4:
+    dependencies:
+      acorn: 8.15.0
 
   acorn@7.4.1: {}
 
-  acorn@8.11.3: {}
+  acorn@8.15.0: {}
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.3.4
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
     optional: true
 
-  agent-base@7.1.1:
-    dependencies:
-      debug: 4.3.4
-    transitivePeerDependencies:
-      - supports-color
+  agent-base@7.1.3: {}
 
-  agentkeepalive@4.5.0:
+  agentkeepalive@4.6.0:
     dependencies:
       humanize-ms: 1.2.1
     optional: true
@@ -7719,13 +7774,13 @@ snapshots:
       clean-stack: 2.2.0
       indent-string: 4.0.0
 
-  ajv-formats@2.1.1(ajv@8.13.0):
+  ajv-formats@2.1.1(ajv@8.17.1):
     optionalDependencies:
-      ajv: 8.13.0
+      ajv: 8.17.1
 
-  ajv-formats@3.0.1(ajv@8.13.0):
+  ajv-formats@3.0.1(ajv@8.17.1):
     optionalDependencies:
-      ajv: 8.13.0
+      ajv: 8.17.1
 
   ajv@6.12.6:
     dependencies:
@@ -7734,12 +7789,12 @@ snapshots:
       json-schema-traverse: 0.4.1
       uri-js: 4.4.1
 
-  ajv@8.13.0:
+  ajv@8.17.1:
     dependencies:
       fast-deep-equal: 3.1.3
+      fast-uri: 3.0.6
       json-schema-traverse: 1.0.0
       require-from-string: 2.0.2
-      uri-js: 4.4.1
 
   ansi-align@3.0.1:
     dependencies:
@@ -7747,11 +7802,9 @@ snapshots:
 
   ansi-escapes@3.2.0: {}
 
-  ansi-escapes@4.3.2:
+  ansi-escapes@7.0.0:
     dependencies:
-      type-fest: 0.21.3
-
-  ansi-escapes@6.2.1: {}
+      environment: 1.1.0
 
   ansi-regex@2.1.1: {}
 
@@ -7761,7 +7814,7 @@ snapshots:
 
   ansi-regex@5.0.1: {}
 
-  ansi-regex@6.0.1: {}
+  ansi-regex@6.1.0: {}
 
   ansi-styles@2.2.1: {}
 
@@ -7773,8 +7826,6 @@ snapshots:
     dependencies:
       color-convert: 2.0.1
 
-  ansi-styles@5.2.0: {}
-
   ansi-styles@6.2.1: {}
 
   any-shell-escape@0.1.1: {}
@@ -7799,10 +7850,10 @@ snapshots:
 
   argparse@2.0.1: {}
 
-  array-buffer-byte-length@1.0.1:
+  array-buffer-byte-length@1.0.2:
     dependencies:
-      call-bind: 1.0.7
-      is-array-buffer: 3.0.4
+      call-bound: 1.0.4
+      is-array-buffer: 3.0.5
 
   array-flatten@3.0.0: {}
 
@@ -7810,63 +7861,65 @@ snapshots:
 
   array-ify@1.0.0: {}
 
-  array-includes@3.1.8:
+  array-includes@3.1.9:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-object-atoms: 1.0.0
-      get-intrinsic: 1.2.4
-      is-string: 1.0.7
+      es-abstract: 1.24.0
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      is-string: 1.1.1
+      math-intrinsics: 1.1.0
+
+  array-timsort@1.0.3: {}
 
   array-union@2.1.0: {}
 
-  array.prototype.findlastindex@1.2.5:
+  array.prototype.findlast@1.2.5:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
+      es-abstract: 1.24.0
       es-errors: 1.3.0
-      es-object-atoms: 1.0.0
-      es-shim-unscopables: 1.0.2
+      es-object-atoms: 1.1.1
+      es-shim-unscopables: 1.1.0
 
-  array.prototype.flat@1.3.2:
+  array.prototype.flat@1.3.3:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-shim-unscopables: 1.0.2
+      es-abstract: 1.24.0
+      es-shim-unscopables: 1.1.0
 
-  array.prototype.flatmap@1.3.2:
+  array.prototype.flatmap@1.3.3:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-shim-unscopables: 1.0.2
+      es-abstract: 1.24.0
+      es-shim-unscopables: 1.1.0
 
-  array.prototype.map@1.0.7:
+  array.prototype.tosorted@1.1.4:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-array-method-boxes-properly: 1.0.0
-      es-object-atoms: 1.0.0
-      is-string: 1.0.7
+      es-abstract: 1.24.0
+      es-errors: 1.3.0
+      es-shim-unscopables: 1.1.0
 
-  arraybuffer.prototype.slice@1.0.3:
+  arraybuffer.prototype.slice@1.0.4:
     dependencies:
-      array-buffer-byte-length: 1.0.1
-      call-bind: 1.0.7
+      array-buffer-byte-length: 1.0.2
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
+      es-abstract: 1.24.0
       es-errors: 1.3.0
-      get-intrinsic: 1.2.4
-      is-array-buffer: 3.0.4
-      is-shared-array-buffer: 1.0.3
+      get-intrinsic: 1.3.0
+      is-array-buffer: 3.0.5
 
   asn1.js@4.10.1:
     dependencies:
-      bn.js: 4.12.0
+      bn.js: 4.12.2
       inherits: 2.0.4
       minimalistic-assert: 1.0.1
 
@@ -7878,24 +7931,24 @@ snapshots:
 
   assert@1.5.1:
     dependencies:
-      object.assign: 4.1.5
+      object.assign: 4.1.7
       util: 0.10.4
 
-  assertion-error@1.1.0: {}
+  assertion-error@2.0.1: {}
 
-  ast-types@0.13.4:
+  ast-v8-to-istanbul@0.3.3:
     dependencies:
-      tslib: 2.6.2
+      '@jridgewell/trace-mapping': 0.3.29
+      estree-walker: 3.0.3
+      js-tokens: 9.0.1
 
-  async-retry@1.3.3:
-    dependencies:
-      retry: 0.13.1
+  async-function@1.0.0: {}
 
   async@2.6.4:
     dependencies:
       lodash: 4.17.21
 
-  async@3.2.5: {}
+  async@3.2.6: {}
 
   asynckit@0.4.0: {}
 
@@ -7903,16 +7956,6 @@ snapshots:
 
   atomically@1.7.0: {}
 
-  auto-changelog@2.4.0(encoding@0.1.13):
-    dependencies:
-      commander: 7.2.0
-      handlebars: 4.7.8
-      node-fetch: 2.7.0(encoding@0.1.13)
-      parse-github-url: 1.0.2
-      semver: 7.6.2
-    transitivePeerDependencies:
-      - encoding
-
   autocannon@7.15.0:
     dependencies:
       chalk: 4.1.2
@@ -7920,12 +7963,12 @@ snapshots:
       cli-table3: 0.6.5
       color-support: 1.1.3
       cross-argv: 2.0.0
-      form-data: 4.0.0
+      form-data: 4.0.3
       has-async-hooks: 1.0.0
-      hdr-histogram-js: 3.0.0
+      hdr-histogram-js: 3.0.1
       hdr-histogram-percentiles-obj: 3.0.0
-      http-parser-js: 0.5.8
-      hyperid: 3.2.0
+      http-parser-js: 0.5.10
+      hyperid: 3.3.0
       lodash.chunk: 4.2.0
       lodash.clonedeep: 4.5.0
       lodash.flatten: 4.4.0
@@ -7935,19 +7978,19 @@ snapshots:
       progress: 2.0.3
       reinterval: 1.1.0
       retimer: 3.0.0
-      semver: 7.6.2
+      semver: 7.7.2
       subarg: 1.0.0
       timestring: 6.0.0
 
   available-typed-arrays@1.0.7:
     dependencies:
-      possible-typed-array-names: 1.0.0
+      possible-typed-array-names: 1.1.0
 
   aws-sign2@0.7.0: {}
 
-  aws4@1.13.0: {}
+  aws4@1.13.2: {}
 
-  b4a@1.6.6: {}
+  b4a@1.6.7: {}
 
   balanced-match@1.0.2: {}
 
@@ -7959,8 +8002,6 @@ snapshots:
     dependencies:
       tweetnacl: 0.14.5
 
-  before-after-hook@2.2.3: {}
-
   binary-extensions@2.3.0: {}
 
   bindings@1.5.0:
@@ -7975,9 +8016,9 @@ snapshots:
       inherits: 2.0.4
       readable-stream: 3.6.2
 
-  bn.js@4.12.0: {}
+  bn.js@4.12.2: {}
 
-  bn.js@5.2.1: {}
+  bn.js@5.2.2: {}
 
   boolbase@1.0.0: {}
 
@@ -7992,23 +8033,12 @@ snapshots:
       widest-line: 3.1.0
       wrap-ansi: 7.0.0
 
-  boxen@7.1.1:
-    dependencies:
-      ansi-align: 3.0.1
-      camelcase: 7.0.1
-      chalk: 5.3.0
-      cli-boxes: 3.0.0
-      string-width: 5.1.2
-      type-fest: 2.19.0
-      widest-line: 4.0.1
-      wrap-ansi: 8.1.0
-
-  brace-expansion@1.1.11:
+  brace-expansion@1.1.12:
     dependencies:
       balanced-match: 1.0.2
       concat-map: 0.0.1
 
-  brace-expansion@2.0.1:
+  brace-expansion@2.0.2:
     dependencies:
       balanced-match: 1.0.2
 
@@ -8019,7 +8049,7 @@ snapshots:
   brfs@2.0.2:
     dependencies:
       quote-stream: 1.0.2
-      resolve: 1.22.8
+      resolve: 1.22.10
       static-module: 3.0.4
       through2: 2.0.5
 
@@ -8038,12 +8068,12 @@ snapshots:
 
   browser-resolve@2.0.0:
     dependencies:
-      resolve: 1.22.8
+      resolve: 1.22.10
 
   browserify-aes@1.2.0:
     dependencies:
       buffer-xor: 1.0.3
-      cipher-base: 1.0.4
+      cipher-base: 1.0.6
       create-hash: 1.2.0
       evp_bytestokey: 1.0.3
       inherits: 2.0.4
@@ -8057,24 +8087,25 @@ snapshots:
 
   browserify-des@1.0.2:
     dependencies:
-      cipher-base: 1.0.4
+      cipher-base: 1.0.6
       des.js: 1.1.0
       inherits: 2.0.4
       safe-buffer: 5.2.1
 
-  browserify-rsa@4.1.0:
+  browserify-rsa@4.1.1:
     dependencies:
-      bn.js: 5.2.1
+      bn.js: 5.2.2
       randombytes: 2.1.0
+      safe-buffer: 5.2.1
 
   browserify-sign@4.2.3:
     dependencies:
-      bn.js: 5.2.1
-      browserify-rsa: 4.1.0
+      bn.js: 5.2.2
+      browserify-rsa: 4.1.1
       create-hash: 1.2.0
       create-hmac: 1.1.7
-      elliptic: 6.5.5
-      hash-base: 3.0.4
+      elliptic: 6.6.1
+      hash-base: 3.0.5
       inherits: 2.0.4
       parse-asn1: 5.1.7
       readable-stream: 2.3.8
@@ -8084,7 +8115,7 @@ snapshots:
     dependencies:
       pako: 1.0.11
 
-  browserify@17.0.0:
+  browserify@17.0.1:
     dependencies:
       JSONStream: 1.3.5
       assert: 1.5.1
@@ -8096,14 +8127,14 @@ snapshots:
       concat-stream: 1.6.2
       console-browserify: 1.2.0
       constants-browserify: 1.0.0
-      crypto-browserify: 3.12.0
+      crypto-browserify: 3.12.1
       defined: 1.0.1
       deps-sort: 2.0.1
       domain-browser: 1.2.0
       duplexer2: 0.1.4
       events: 3.3.0
       glob: 7.2.3
-      has: 1.0.4
+      hasown: 2.0.2
       htmlescape: 1.1.1
       https-browserify: 1.0.0
       inherits: 2.0.4
@@ -8119,9 +8150,9 @@ snapshots:
       querystring-es3: 0.2.1
       read-only-stream: 2.0.0
       readable-stream: 2.3.8
-      resolve: 1.22.8
+      resolve: 1.22.10
       shasum-object: 1.0.0
-      shell-quote: 1.8.1
+      shell-quote: 1.8.3
       stream-browserify: 3.0.0
       stream-http: 3.2.0
       string_decoder: 1.3.0
@@ -8130,19 +8161,19 @@ snapshots:
       through2: 2.0.5
       timers-browserify: 1.4.2
       tty-browserify: 0.0.1
-      url: 0.11.3
+      url: 0.11.4
       util: 0.12.5
       vm-browserify: 1.1.2
       xtend: 4.0.2
 
-  browserslist@4.23.0:
+  browserslist@4.25.1:
     dependencies:
-      caniuse-lite: 1.0.30001620
-      electron-to-chromium: 1.4.776
-      node-releases: 2.0.14
-      update-browserslist-db: 1.0.16(browserslist@4.23.0)
+      caniuse-lite: 1.0.30001726
+      electron-to-chromium: 1.5.179
+      node-releases: 2.0.19
+      update-browserslist-db: 1.1.3(browserslist@4.25.1)
 
-  bson@6.7.0: {}
+  bson@6.10.4: {}
 
   buffer-equal@0.0.1: {}
 
@@ -8160,30 +8191,24 @@ snapshots:
       base64-js: 1.5.1
       ieee754: 1.2.1
 
-  bufferutil@4.0.8:
+  bufferutil@4.0.9:
     dependencies:
-      node-gyp-build: 4.8.1
+      node-gyp-build: 4.8.4
     optional: true
 
-  builtin-modules@3.3.0: {}
-
   builtin-status-codes@3.0.0: {}
 
-  bundle-name@4.1.0:
-    dependencies:
-      run-applescript: 7.0.0
-
-  c8@9.1.0:
+  c8@10.1.3:
     dependencies:
-      '@bcoe/v8-coverage': 0.2.3
+      '@bcoe/v8-coverage': 1.0.2
       '@istanbuljs/schema': 0.1.3
       find-up: 5.0.0
-      foreground-child: 3.1.1
+      foreground-child: 3.3.1
       istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
       istanbul-reports: 3.1.7
-      test-exclude: 6.0.0
-      v8-to-istanbul: 9.2.0
+      test-exclude: 7.0.1
+      v8-to-istanbul: 9.3.0
       yargs: 17.7.2
       yargs-parser: 21.1.1
 
@@ -8219,22 +8244,31 @@ snapshots:
     dependencies:
       '@types/http-cache-semantics': 4.0.4
       get-stream: 6.0.1
-      http-cache-semantics: 4.1.1
+      http-cache-semantics: 4.2.0
       keyv: 4.5.4
       mimic-response: 4.0.0
-      normalize-url: 8.0.1
+      normalize-url: 8.0.2
       responselike: 3.0.0
 
   cached-path-relative@1.1.0: {}
 
-  call-bind@1.0.7:
+  call-bind-apply-helpers@1.0.2:
     dependencies:
-      es-define-property: 1.0.0
       es-errors: 1.3.0
       function-bind: 1.1.2
-      get-intrinsic: 1.2.4
+
+  call-bind@1.0.8:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
+      get-intrinsic: 1.3.0
       set-function-length: 1.2.2
 
+  call-bound@1.0.4:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      get-intrinsic: 1.3.0
+
   callsites@3.1.0: {}
 
   camel-case@3.0.0:
@@ -8246,23 +8280,19 @@ snapshots:
 
   camelcase@6.3.0: {}
 
-  camelcase@7.0.1: {}
-
-  caniuse-lite@1.0.30001620: {}
+  caniuse-lite@1.0.30001726: {}
 
   caseless@0.12.0: {}
 
   cephes@2.0.0: {}
 
-  chai@4.4.1:
+  chai@5.2.0:
     dependencies:
-      assertion-error: 1.1.0
-      check-error: 1.0.3
-      deep-eql: 4.1.3
-      get-func-name: 2.0.2
-      loupe: 2.3.7
-      pathval: 1.1.1
-      type-detect: 4.0.8
+      assertion-error: 2.0.1
+      check-error: 2.1.1
+      deep-eql: 5.0.2
+      loupe: 3.1.4
+      pathval: 2.0.1
 
   chalk@1.1.3:
     dependencies:
@@ -8283,15 +8313,13 @@ snapshots:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
-  chalk@5.3.0: {}
+  chalk@5.4.1: {}
 
   char-spinner@1.0.1: {}
 
   chardet@0.7.0: {}
 
-  check-error@1.0.3:
-    dependencies:
-      get-func-name: 2.0.2
+  check-error@2.1.1: {}
 
   chokidar@3.6.0:
     dependencies:
@@ -8313,18 +8341,19 @@ snapshots:
 
   ci-info@2.0.0: {}
 
-  ci-info@3.9.0: {}
-
-  cipher-base@1.0.4:
+  cipher-base@1.0.6:
     dependencies:
       inherits: 2.0.4
       safe-buffer: 5.2.1
 
   clean-stack@2.2.0: {}
 
-  cli-boxes@2.2.1: {}
+  clear-module@4.1.2:
+    dependencies:
+      parent-module: 2.0.0
+      resolve-from: 5.0.0
 
-  cli-boxes@3.0.0: {}
+  cli-boxes@2.2.1: {}
 
   cli-cursor@2.1.0:
     dependencies:
@@ -8334,9 +8363,9 @@ snapshots:
     dependencies:
       restore-cursor: 3.1.0
 
-  cli-cursor@4.0.0:
+  cli-cursor@5.0.0:
     dependencies:
-      restore-cursor: 4.0.0
+      restore-cursor: 5.1.0
 
   cli-spinners@2.9.2: {}
 
@@ -8349,12 +8378,10 @@ snapshots:
   cli-truncate@4.0.0:
     dependencies:
       slice-ansi: 5.0.0
-      string-width: 7.1.0
+      string-width: 7.2.0
 
   cli-width@2.2.1: {}
 
-  cli-width@4.1.0: {}
-
   clinic@13.0.0(encoding@0.1.13):
     dependencies:
       '@clinic/bubbleprof': 10.0.0
@@ -8362,7 +8389,7 @@ snapshots:
       '@clinic/flame': 13.0.0
       '@clinic/heap-profiler': 5.0.0
       any-shell-escape: 0.1.1
-      async: 3.2.5
+      async: 3.2.6
       autocannon: 7.15.0
       commist: 1.1.0
       cross-argv: 1.0.0
@@ -8397,7 +8424,7 @@ snapshots:
 
   clone@1.0.4: {}
 
-  code-block-writer@13.0.1: {}
+  code-block-writer@13.0.3: {}
 
   code-point-at@1.1.0: {}
 
@@ -8447,11 +8474,17 @@ snapshots:
 
   commander@10.0.1: {}
 
-  commander@11.1.0: {}
+  commander@14.0.0: {}
 
   commander@2.20.3: {}
 
-  commander@7.2.0: {}
+  comment-json@4.2.5:
+    dependencies:
+      array-timsort: 1.0.3
+      core-util-is: 1.0.3
+      esprima: 4.0.1
+      has-own-prop: 2.0.0
+      repeat-string: 1.6.1
 
   comment-parser@1.4.1: {}
 
@@ -8483,8 +8516,8 @@ snapshots:
 
   conf@10.2.0:
     dependencies:
-      ajv: 8.13.0
-      ajv-formats: 2.1.1(ajv@8.13.0)
+      ajv: 8.17.1
+      ajv-formats: 2.1.1(ajv@8.17.1)
       atomically: 1.7.0
       debounce-fn: 4.0.0
       dot-prop: 6.0.1
@@ -8492,9 +8525,7 @@ snapshots:
       json-schema-typed: 7.0.3
       onetime: 5.1.2
       pkg-up: 3.1.0
-      semver: 7.6.2
-
-  confbox@0.1.7: {}
+      semver: 7.7.2
 
   config-chain@1.1.13:
     dependencies:
@@ -8510,14 +8541,6 @@ snapshots:
       write-file-atomic: 3.0.3
       xdg-basedir: 4.0.0
 
-  configstore@6.0.0:
-    dependencies:
-      dot-prop: 6.0.1
-      graceful-fs: 4.2.11
-      unique-string: 3.0.0
-      write-file-atomic: 3.0.3
-      xdg-basedir: 5.1.0
-
   console-browserify@1.2.0: {}
 
   console-control-strings@1.1.0:
@@ -8554,43 +8577,50 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.12)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5):
+  cosmiconfig-typescript-loader@6.1.0(@types/node@24.0.10)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3):
     dependencies:
-      '@types/node': 20.12.12
-      cosmiconfig: 9.0.0(typescript@5.4.5)
-      jiti: 1.21.0
-      typescript: 5.4.5
+      '@types/node': 24.0.10
+      cosmiconfig: 9.0.0(typescript@5.8.3)
+      jiti: 2.4.2
+      typescript: 5.8.3
 
-  cosmiconfig@9.0.0(typescript@5.4.5):
+  cosmiconfig@9.0.0(typescript@5.8.3):
     dependencies:
       env-paths: 2.2.1
-      import-fresh: 3.3.0
+      import-fresh: 3.3.1
       js-yaml: 4.1.0
       parse-json: 5.2.0
     optionalDependencies:
-      typescript: 5.4.5
+      typescript: 5.8.3
 
   create-ecdh@4.0.4:
     dependencies:
-      bn.js: 4.12.0
-      elliptic: 6.5.5
+      bn.js: 4.12.2
+      elliptic: 6.6.1
+
+  create-hash@1.1.3:
+    dependencies:
+      cipher-base: 1.0.6
+      inherits: 2.0.4
+      ripemd160: 2.0.2
+      sha.js: 2.4.12
 
   create-hash@1.2.0:
     dependencies:
-      cipher-base: 1.0.4
+      cipher-base: 1.0.6
       inherits: 2.0.4
       md5.js: 1.3.5
       ripemd160: 2.0.2
-      sha.js: 2.4.11
+      sha.js: 2.4.12
 
   create-hmac@1.1.7:
     dependencies:
-      cipher-base: 1.0.4
+      cipher-base: 1.0.6
       create-hash: 1.2.0
       inherits: 2.0.4
       ripemd160: 2.0.2
       safe-buffer: 5.2.1
-      sha.js: 2.4.11
+      sha.js: 2.4.12
 
   create-require@1.1.1: {}
 
@@ -8600,15 +8630,15 @@ snapshots:
 
   cross-env@7.0.3:
     dependencies:
-      cross-spawn: 7.0.3
+      cross-spawn: 7.0.6
 
-  cross-spawn@7.0.3:
+  cross-spawn@7.0.6:
     dependencies:
       path-key: 3.1.1
       shebang-command: 2.0.0
       which: 2.0.2
 
-  crypto-browserify@3.12.0:
+  crypto-browserify@3.12.1:
     dependencies:
       browserify-cipher: 1.0.1
       browserify-sign: 4.2.3
@@ -8616,23 +8646,82 @@ snapshots:
       create-hash: 1.2.0
       create-hmac: 1.1.7
       diffie-hellman: 5.0.3
+      hash-base: 3.0.5
       inherits: 2.0.4
-      pbkdf2: 3.1.2
+      pbkdf2: 3.1.3
       public-encrypt: 4.0.3
       randombytes: 2.1.0
       randomfill: 1.0.4
 
   crypto-random-string@2.0.0: {}
 
-  crypto-random-string@4.0.0:
+  cspell-config-lib@9.1.2:
+    dependencies:
+      '@cspell/cspell-types': 9.1.2
+      comment-json: 4.2.5
+      yaml: 2.8.0
+
+  cspell-dictionary@9.1.2:
+    dependencies:
+      '@cspell/cspell-pipe': 9.1.2
+      '@cspell/cspell-types': 9.1.2
+      cspell-trie-lib: 9.1.2
+      fast-equals: 5.2.2
+
+  cspell-glob@9.1.2:
+    dependencies:
+      '@cspell/url': 9.1.2
+      picomatch: 4.0.2
+
+  cspell-grammar@9.1.2:
+    dependencies:
+      '@cspell/cspell-pipe': 9.1.2
+      '@cspell/cspell-types': 9.1.2
+
+  cspell-io@9.1.2:
+    dependencies:
+      '@cspell/cspell-service-bus': 9.1.2
+      '@cspell/url': 9.1.2
+
+  cspell-lib@9.1.2:
+    dependencies:
+      '@cspell/cspell-bundled-dicts': 9.1.2
+      '@cspell/cspell-pipe': 9.1.2
+      '@cspell/cspell-resolver': 9.1.2
+      '@cspell/cspell-types': 9.1.2
+      '@cspell/dynamic-import': 9.1.2
+      '@cspell/filetypes': 9.1.2
+      '@cspell/strong-weak-map': 9.1.2
+      '@cspell/url': 9.1.2
+      clear-module: 4.1.2
+      comment-json: 4.2.5
+      cspell-config-lib: 9.1.2
+      cspell-dictionary: 9.1.2
+      cspell-glob: 9.1.2
+      cspell-grammar: 9.1.2
+      cspell-io: 9.1.2
+      cspell-trie-lib: 9.1.2
+      env-paths: 3.0.0
+      fast-equals: 5.2.2
+      gensequence: 7.0.0
+      import-fresh: 3.3.1
+      resolve-from: 5.0.0
+      vscode-languageserver-textdocument: 1.0.12
+      vscode-uri: 3.1.0
+      xdg-basedir: 5.1.0
+
+  cspell-trie-lib@9.1.2:
     dependencies:
-      type-fest: 1.4.0
+      '@cspell/cspell-pipe': 9.1.2
+      '@cspell/cspell-types': 9.1.2
+      gensequence: 7.0.0
 
   cssesc@3.0.0: {}
 
-  cssstyle@4.0.1:
+  cssstyle@4.6.0:
     dependencies:
-      rrweb-cssom: 0.6.0
+      '@asamuzakjp/css-color': 3.2.0
+      rrweb-cssom: 0.8.0
 
   csstype@3.1.3: {}
 
@@ -8725,7 +8814,7 @@ snapshots:
   d@1.0.2:
     dependencies:
       es5-ext: 0.10.64
-      type: 2.7.2
+      type: 2.7.3
 
   dargs@7.0.0: {}
 
@@ -8739,36 +8828,32 @@ snapshots:
     dependencies:
       assert-plus: 1.0.0
 
-  data-uri-to-buffer@4.0.1: {}
-
-  data-uri-to-buffer@6.0.2: {}
-
   data-urls@5.0.0:
     dependencies:
       whatwg-mimetype: 4.0.0
-      whatwg-url: 14.0.0
+      whatwg-url: 14.2.0
 
-  data-view-buffer@1.0.1:
+  data-view-buffer@1.0.2:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      is-data-view: 1.0.1
+      is-data-view: 1.0.2
 
-  data-view-byte-length@1.0.1:
+  data-view-byte-length@1.0.2:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      is-data-view: 1.0.1
+      is-data-view: 1.0.2
 
-  data-view-byte-offset@1.0.0:
+  data-view-byte-offset@1.0.1:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      is-data-view: 1.0.1
+      is-data-view: 1.0.2
 
-  dataloader@2.2.2: {}
+  dataloader@2.2.3: {}
 
-  date-fns@3.6.0: {}
+  date-fns@4.1.0: {}
 
   debounce-fn@4.0.0:
     dependencies:
@@ -8776,41 +8861,33 @@ snapshots:
 
   debounce@1.2.1: {}
 
-  debug@2.6.9:
-    dependencies:
-      ms: 2.0.0
-
   debug@3.2.7:
     dependencies:
       ms: 2.1.3
+    optional: true
 
   debug@4.3.4:
     dependencies:
       ms: 2.1.2
 
+  debug@4.4.1:
+    dependencies:
+      ms: 2.1.3
+
   decamelize@1.2.0: {}
 
-  decimal.js@10.4.3: {}
+  decimal.js@10.5.0: {}
 
   decompress-response@6.0.0:
     dependencies:
       mimic-response: 3.1.0
 
-  deep-eql@4.1.3:
-    dependencies:
-      type-detect: 4.0.8
+  deep-eql@5.0.2: {}
 
   deep-extend@0.6.0: {}
 
   deep-is@0.1.4: {}
 
-  default-browser-id@5.0.0: {}
-
-  default-browser@5.2.1:
-    dependencies:
-      bundle-name: 4.1.0
-      default-browser-id: 5.0.0
-
   defaults@1.0.4:
     dependencies:
       clone: 1.0.4
@@ -8819,11 +8896,9 @@ snapshots:
 
   define-data-property@1.1.4:
     dependencies:
-      es-define-property: 1.0.0
+      es-define-property: 1.0.1
       es-errors: 1.3.0
-      gopd: 1.0.1
-
-  define-lazy-prop@3.0.0: {}
+      gopd: 1.2.0
 
   define-properties@1.2.1:
     dependencies:
@@ -8833,12 +8908,6 @@ snapshots:
 
   defined@1.0.1: {}
 
-  degenerator@5.0.1:
-    dependencies:
-      ast-types: 0.13.4
-      escodegen: 2.1.0
-      esprima: 4.0.1
-
   del@6.1.1:
     dependencies:
       globby: 11.1.0
@@ -8859,8 +8928,6 @@ snapshots:
 
   depd@2.0.0: {}
 
-  deprecation@2.3.1: {}
-
   deps-sort@2.0.1:
     dependencies:
       JSONStream: 1.3.5
@@ -8873,11 +8940,7 @@ snapshots:
       inherits: 2.0.4
       minimalistic-assert: 1.0.1
 
-  destroy@1.2.0: {}
-
-  detect-indent@7.0.1: {}
-
-  detect-libc@2.0.3: {}
+  detect-libc@2.0.4: {}
 
   detective@5.2.1:
     dependencies:
@@ -8885,13 +8948,11 @@ snapshots:
       defined: 1.0.1
       minimist: 1.2.8
 
-  diff-sequences@29.6.3: {}
-
   diff@4.0.2: {}
 
   diffie-hellman@5.0.3:
     dependencies:
-      bn.js: 4.12.0
+      bn.js: 4.12.2
       miller-rabin: 4.0.1
       randombytes: 2.1.0
 
@@ -8907,10 +8968,6 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
-  doctrine@3.0.0:
-    dependencies:
-      esutils: 2.0.3
-
   domain-browser@1.2.0: {}
 
   dot-prop@5.3.0:
@@ -8921,7 +8978,13 @@ snapshots:
     dependencies:
       is-obj: 2.0.0
 
-  dotenv@16.4.5: {}
+  dotenv@16.5.0: {}
+
+  dunder-proto@1.0.1:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-errors: 1.3.0
+      gopd: 1.2.0
 
   dup@1.0.0: {}
 
@@ -8931,7 +8994,7 @@ snapshots:
 
   duplexify@4.1.3:
     dependencies:
-      end-of-stream: 1.4.4
+      end-of-stream: 1.4.5
       inherits: 2.0.4
       readable-stream: 3.6.2
       stream-shift: 1.0.3
@@ -8948,15 +9011,15 @@ snapshots:
       '@one-ini/wasm': 0.1.1
       commander: 10.0.1
       minimatch: 9.0.1
-      semver: 7.6.2
+      semver: 7.7.2
 
   ee-first@1.1.1: {}
 
-  electron-to-chromium@1.4.776: {}
+  electron-to-chromium@1.5.179: {}
 
-  elliptic@6.5.5:
+  elliptic@6.6.1:
     dependencies:
-      bn.js: 4.12.0
+      bn.js: 4.12.2
       brorand: 1.1.0
       hash.js: 1.1.7
       hmac-drbg: 1.0.1
@@ -8964,7 +9027,7 @@ snapshots:
       minimalistic-assert: 1.0.1
       minimalistic-crypto-utils: 1.0.1
 
-  emoji-regex@10.3.0: {}
+  emoji-regex@10.4.0: {}
 
   emoji-regex@7.0.3: {}
 
@@ -8974,14 +9037,14 @@ snapshots:
 
   enabled@2.0.0: {}
 
-  encodeurl@1.0.2: {}
+  encodeurl@2.0.0: {}
 
   encoding@0.1.13:
     dependencies:
       iconv-lite: 0.6.3
     optional: true
 
-  end-of-stream@1.4.4:
+  end-of-stream@1.4.5:
     dependencies:
       once: 1.4.0
 
@@ -8989,17 +9052,23 @@ snapshots:
     dependencies:
       inherits: 2.0.4
 
-  enhanced-resolve@5.16.1:
+  enhanced-resolve@5.18.2:
     dependencies:
       graceful-fs: 4.2.11
-      tapable: 2.2.1
+      tapable: 2.2.2
 
   entities@4.5.0: {}
 
+  entities@6.0.1: {}
+
   env-paths@2.2.1: {}
 
+  env-paths@3.0.0: {}
+
   env-string@1.0.1: {}
 
+  environment@1.1.0: {}
+
   err-code@2.0.3:
     optional: true
 
@@ -9007,94 +9076,108 @@ snapshots:
     dependencies:
       is-arrayish: 0.2.1
 
-  es-abstract@1.23.3:
+  es-abstract@1.24.0:
     dependencies:
-      array-buffer-byte-length: 1.0.1
-      arraybuffer.prototype.slice: 1.0.3
+      array-buffer-byte-length: 1.0.2
+      arraybuffer.prototype.slice: 1.0.4
       available-typed-arrays: 1.0.7
-      call-bind: 1.0.7
-      data-view-buffer: 1.0.1
-      data-view-byte-length: 1.0.1
-      data-view-byte-offset: 1.0.0
-      es-define-property: 1.0.0
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      data-view-buffer: 1.0.2
+      data-view-byte-length: 1.0.2
+      data-view-byte-offset: 1.0.1
+      es-define-property: 1.0.1
       es-errors: 1.3.0
-      es-object-atoms: 1.0.0
-      es-set-tostringtag: 2.0.3
-      es-to-primitive: 1.2.1
-      function.prototype.name: 1.1.6
-      get-intrinsic: 1.2.4
-      get-symbol-description: 1.0.2
+      es-object-atoms: 1.1.1
+      es-set-tostringtag: 2.1.0
+      es-to-primitive: 1.3.0
+      function.prototype.name: 1.1.8
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      get-symbol-description: 1.1.0
       globalthis: 1.0.4
-      gopd: 1.0.1
+      gopd: 1.2.0
       has-property-descriptors: 1.0.2
-      has-proto: 1.0.3
-      has-symbols: 1.0.3
+      has-proto: 1.2.0
+      has-symbols: 1.1.0
       hasown: 2.0.2
-      internal-slot: 1.0.7
-      is-array-buffer: 3.0.4
+      internal-slot: 1.1.0
+      is-array-buffer: 3.0.5
       is-callable: 1.2.7
-      is-data-view: 1.0.1
+      is-data-view: 1.0.2
       is-negative-zero: 2.0.3
-      is-regex: 1.1.4
-      is-shared-array-buffer: 1.0.3
-      is-string: 1.0.7
-      is-typed-array: 1.1.13
-      is-weakref: 1.0.2
-      object-inspect: 1.13.1
+      is-regex: 1.2.1
+      is-set: 2.0.3
+      is-shared-array-buffer: 1.0.4
+      is-string: 1.1.1
+      is-typed-array: 1.1.15
+      is-weakref: 1.1.1
+      math-intrinsics: 1.1.0
+      object-inspect: 1.13.4
       object-keys: 1.1.1
-      object.assign: 4.1.5
-      regexp.prototype.flags: 1.5.2
-      safe-array-concat: 1.1.2
-      safe-regex-test: 1.0.3
-      string.prototype.trim: 1.2.9
-      string.prototype.trimend: 1.0.8
+      object.assign: 4.1.7
+      own-keys: 1.0.1
+      regexp.prototype.flags: 1.5.4
+      safe-array-concat: 1.1.3
+      safe-push-apply: 1.0.0
+      safe-regex-test: 1.1.0
+      set-proto: 1.0.0
+      stop-iteration-iterator: 1.1.0
+      string.prototype.trim: 1.2.10
+      string.prototype.trimend: 1.0.9
       string.prototype.trimstart: 1.0.8
-      typed-array-buffer: 1.0.2
-      typed-array-byte-length: 1.0.1
-      typed-array-byte-offset: 1.0.2
-      typed-array-length: 1.0.6
-      unbox-primitive: 1.0.2
-      which-typed-array: 1.1.15
+      typed-array-buffer: 1.0.3
+      typed-array-byte-length: 1.0.3
+      typed-array-byte-offset: 1.0.4
+      typed-array-length: 1.0.7
+      unbox-primitive: 1.1.0
+      which-typed-array: 1.1.19
 
-  es-array-method-boxes-properly@1.0.0: {}
-
-  es-define-property@1.0.0:
-    dependencies:
-      get-intrinsic: 1.2.4
+  es-define-property@1.0.1: {}
 
   es-errors@1.3.0: {}
 
-  es-get-iterator@1.1.3:
+  es-iterator-helpers@1.2.1:
     dependencies:
-      call-bind: 1.0.7
-      get-intrinsic: 1.2.4
-      has-symbols: 1.0.3
-      is-arguments: 1.1.1
-      is-map: 2.0.3
-      is-set: 2.0.3
-      is-string: 1.0.7
-      isarray: 2.0.5
-      stop-iteration-iterator: 1.0.0
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.0
+      es-errors: 1.3.0
+      es-set-tostringtag: 2.1.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.3.0
+      globalthis: 1.0.4
+      gopd: 1.2.0
+      has-property-descriptors: 1.0.2
+      has-proto: 1.2.0
+      has-symbols: 1.1.0
+      internal-slot: 1.1.0
+      iterator.prototype: 1.1.5
+      safe-array-concat: 1.1.3
 
-  es-object-atoms@1.0.0:
+  es-module-lexer@1.7.0: {}
+
+  es-object-atoms@1.1.1:
     dependencies:
       es-errors: 1.3.0
 
-  es-set-tostringtag@2.0.3:
+  es-set-tostringtag@2.1.0:
     dependencies:
-      get-intrinsic: 1.2.4
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
       has-tostringtag: 1.0.2
       hasown: 2.0.2
 
-  es-shim-unscopables@1.0.2:
+  es-shim-unscopables@1.1.0:
     dependencies:
       hasown: 2.0.2
 
-  es-to-primitive@1.2.1:
+  es-to-primitive@1.3.0:
     dependencies:
       is-callable: 1.2.7
-      is-date-object: 1.0.5
-      is-symbol: 1.0.4
+      is-date-object: 1.1.0
+      is-symbol: 1.1.1
 
   es5-ext@0.10.64:
     dependencies:
@@ -9125,91 +9208,63 @@ snapshots:
       es6-iterator: 2.0.3
       es6-symbol: 3.1.4
       event-emitter: 0.3.5
-      type: 2.7.2
+      type: 2.7.3
 
   es6-symbol@3.1.4:
     dependencies:
       d: 1.0.2
       ext: 1.7.0
 
-  esbuild-plugin-clean@1.0.1(esbuild@0.21.3):
+  esbuild-plugin-clean@1.0.1(esbuild@0.25.5):
     dependencies:
       chalk: 4.1.2
       del: 6.1.1
-      esbuild: 0.21.3
+      esbuild: 0.25.5
 
-  esbuild-plugin-copy@2.1.1(esbuild@0.21.3):
+  esbuild-plugin-copy@2.1.1(esbuild@0.25.5):
     dependencies:
       chalk: 4.1.2
       chokidar: 3.6.0
-      esbuild: 0.21.3
+      esbuild: 0.25.5
       fs-extra: 10.1.0
       globby: 11.1.0
 
-  esbuild@0.20.2:
+  esbuild@0.25.5:
     optionalDependencies:
-      '@esbuild/aix-ppc64': 0.20.2
-      '@esbuild/android-arm': 0.20.2
-      '@esbuild/android-arm64': 0.20.2
-      '@esbuild/android-x64': 0.20.2
-      '@esbuild/darwin-arm64': 0.20.2
-      '@esbuild/darwin-x64': 0.20.2
-      '@esbuild/freebsd-arm64': 0.20.2
-      '@esbuild/freebsd-x64': 0.20.2
-      '@esbuild/linux-arm': 0.20.2
-      '@esbuild/linux-arm64': 0.20.2
-      '@esbuild/linux-ia32': 0.20.2
-      '@esbuild/linux-loong64': 0.20.2
-      '@esbuild/linux-mips64el': 0.20.2
-      '@esbuild/linux-ppc64': 0.20.2
-      '@esbuild/linux-riscv64': 0.20.2
-      '@esbuild/linux-s390x': 0.20.2
-      '@esbuild/linux-x64': 0.20.2
-      '@esbuild/netbsd-x64': 0.20.2
-      '@esbuild/openbsd-x64': 0.20.2
-      '@esbuild/sunos-x64': 0.20.2
-      '@esbuild/win32-arm64': 0.20.2
-      '@esbuild/win32-ia32': 0.20.2
-      '@esbuild/win32-x64': 0.20.2
-
-  esbuild@0.21.3:
-    optionalDependencies:
-      '@esbuild/aix-ppc64': 0.21.3
-      '@esbuild/android-arm': 0.21.3
-      '@esbuild/android-arm64': 0.21.3
-      '@esbuild/android-x64': 0.21.3
-      '@esbuild/darwin-arm64': 0.21.3
-      '@esbuild/darwin-x64': 0.21.3
-      '@esbuild/freebsd-arm64': 0.21.3
-      '@esbuild/freebsd-x64': 0.21.3
-      '@esbuild/linux-arm': 0.21.3
-      '@esbuild/linux-arm64': 0.21.3
-      '@esbuild/linux-ia32': 0.21.3
-      '@esbuild/linux-loong64': 0.21.3
-      '@esbuild/linux-mips64el': 0.21.3
-      '@esbuild/linux-ppc64': 0.21.3
-      '@esbuild/linux-riscv64': 0.21.3
-      '@esbuild/linux-s390x': 0.21.3
-      '@esbuild/linux-x64': 0.21.3
-      '@esbuild/netbsd-x64': 0.21.3
-      '@esbuild/openbsd-x64': 0.21.3
-      '@esbuild/sunos-x64': 0.21.3
-      '@esbuild/win32-arm64': 0.21.3
-      '@esbuild/win32-ia32': 0.21.3
-      '@esbuild/win32-x64': 0.21.3
-
-  escalade@3.1.2: {}
+      '@esbuild/aix-ppc64': 0.25.5
+      '@esbuild/android-arm': 0.25.5
+      '@esbuild/android-arm64': 0.25.5
+      '@esbuild/android-x64': 0.25.5
+      '@esbuild/darwin-arm64': 0.25.5
+      '@esbuild/darwin-x64': 0.25.5
+      '@esbuild/freebsd-arm64': 0.25.5
+      '@esbuild/freebsd-x64': 0.25.5
+      '@esbuild/linux-arm': 0.25.5
+      '@esbuild/linux-arm64': 0.25.5
+      '@esbuild/linux-ia32': 0.25.5
+      '@esbuild/linux-loong64': 0.25.5
+      '@esbuild/linux-mips64el': 0.25.5
+      '@esbuild/linux-ppc64': 0.25.5
+      '@esbuild/linux-riscv64': 0.25.5
+      '@esbuild/linux-s390x': 0.25.5
+      '@esbuild/linux-x64': 0.25.5
+      '@esbuild/netbsd-arm64': 0.25.5
+      '@esbuild/netbsd-x64': 0.25.5
+      '@esbuild/openbsd-arm64': 0.25.5
+      '@esbuild/openbsd-x64': 0.25.5
+      '@esbuild/sunos-x64': 0.25.5
+      '@esbuild/win32-arm64': 0.25.5
+      '@esbuild/win32-ia32': 0.25.5
+      '@esbuild/win32-x64': 0.25.5
+
+  escalade@3.2.0: {}
 
   escape-goat@2.1.1: {}
 
-  escape-goat@4.0.0: {}
-
   escape-html@1.0.3: {}
 
   escape-string-regexp@1.0.5: {}
 
-  escape-string-regexp@2.0.0: {}
-
   escape-string-regexp@4.0.0: {}
 
   escodegen@1.14.3:
@@ -9229,217 +9284,196 @@ snapshots:
     optionalDependencies:
       source-map: 0.6.1
 
-  eslint-compat-utils@0.5.0(eslint@8.57.0):
-    dependencies:
-      eslint: 8.57.0
-      semver: 7.6.2
-
-  eslint-config-love@47.0.0(@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-n@17.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5):
-    dependencies:
-      '@typescript-eslint/eslint-plugin': 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
-      '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      eslint: 8.57.0
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
-      eslint-plugin-n: 17.7.0(eslint@8.57.0)
-      eslint-plugin-promise: 6.1.1(eslint@8.57.0)
-      typescript: 5.4.5
-    transitivePeerDependencies:
-      - supports-color
-
-  eslint-config-prettier@9.1.0(eslint@8.57.0):
+  eslint-compat-utils@0.5.1(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      eslint: 8.57.0
+      eslint: 9.30.1(jiti@2.4.2)
+      semver: 7.7.2
 
-  eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint-plugin-n@17.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0):
+  eslint-import-context@0.1.9(unrs-resolver@1.10.1):
     dependencies:
-      eslint: 8.57.0
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
-      eslint-plugin-n: 17.7.0(eslint@8.57.0)
-      eslint-plugin-promise: 6.1.1(eslint@8.57.0)
-
-  eslint-define-config@2.1.0: {}
+      get-tsconfig: 4.10.1
+      stable-hash-x: 0.2.0
+    optionalDependencies:
+      unrs-resolver: 1.10.1
 
   eslint-import-resolver-node@0.3.9:
     dependencies:
       debug: 3.2.7
-      is-core-module: 2.13.1
-      resolve: 1.22.8
-    transitivePeerDependencies:
-      - supports-color
-
-  eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0):
-    dependencies:
-      debug: 4.3.4
-      enhanced-resolve: 5.16.1
-      eslint: 8.57.0
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
-      fast-glob: 3.3.2
-      get-tsconfig: 4.7.5
-      is-core-module: 2.13.1
-      is-glob: 4.0.3
+      is-core-module: 2.16.1
+      resolve: 1.22.10
     transitivePeerDependencies:
-      - '@typescript-eslint/parser'
-      - eslint-import-resolver-node
-      - eslint-import-resolver-webpack
       - supports-color
+    optional: true
 
-  eslint-module-utils@2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
+  eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      debug: 3.2.7
+      '@nolyfill/is-core-module': 1.0.39
+      debug: 4.4.1
+      eslint: 9.30.1(jiti@2.4.2)
+      get-tsconfig: 4.10.1
+      is-bun-module: 2.0.0
+      stable-hash: 0.0.5
+      tinyglobby: 0.2.14
+      unrs-resolver: 1.10.1
     optionalDependencies:
-      '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
-      eslint: 8.57.0
-      eslint-import-resolver-node: 0.3.9
-      eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-es-x@7.6.0(eslint@8.57.0):
+  eslint-plugin-es-x@7.8.0(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
-      '@eslint-community/regexpp': 4.10.0
-      eslint: 8.57.0
-      eslint-compat-utils: 0.5.0(eslint@8.57.0)
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      '@eslint-community/regexpp': 4.12.1
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-compat-utils: 0.5.1(eslint@9.30.1(jiti@2.4.2))
 
-  eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+  eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      array-includes: 3.1.8
-      array.prototype.findlastindex: 1.2.5
-      array.prototype.flat: 1.3.2
-      array.prototype.flatmap: 1.3.2
-      debug: 3.2.7
-      doctrine: 2.1.0
-      eslint: 8.57.0
-      eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
-      hasown: 2.0.2
-      is-core-module: 2.13.1
+      '@typescript-eslint/types': 8.35.1
+      comment-parser: 1.4.1
+      debug: 4.4.1
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-import-context: 0.1.9(unrs-resolver@1.10.1)
       is-glob: 4.0.3
-      minimatch: 3.1.2
-      object.fromentries: 2.0.8
-      object.groupby: 1.0.3
-      object.values: 1.2.0
-      semver: 7.6.2
-      tsconfig-paths: 3.15.0
+      minimatch: 10.0.3
+      semver: 7.7.2
+      stable-hash-x: 0.2.0
+      unrs-resolver: 1.10.1
     optionalDependencies:
-      '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5)
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint-import-resolver-node: 0.3.9
     transitivePeerDependencies:
-      - eslint-import-resolver-typescript
-      - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-plugin-jsdoc@48.2.5(eslint@8.57.0):
+  eslint-plugin-jsdoc@51.3.3(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      '@es-joy/jsdoccomment': 0.43.0
+      '@es-joy/jsdoccomment': 0.52.0
       are-docs-informative: 0.0.2
       comment-parser: 1.4.1
-      debug: 4.3.4
+      debug: 4.4.1
       escape-string-regexp: 4.0.0
-      eslint: 8.57.0
-      esquery: 1.5.0
-      is-builtin-module: 3.2.1
-      semver: 7.6.2
+      eslint: 9.30.1(jiti@2.4.2)
+      espree: 10.4.0
+      esquery: 1.6.0
+      parse-imports-exports: 0.2.4
+      semver: 7.7.2
       spdx-expression-parse: 4.0.0
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-n@17.7.0(eslint@8.57.0):
-    dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
-      enhanced-resolve: 5.16.1
-      eslint: 8.57.0
-      eslint-plugin-es-x: 7.6.0(eslint@8.57.0)
-      get-tsconfig: 4.7.5
-      globals: 15.3.0
-      ignore: 5.3.1
-      minimatch: 9.0.4
-      semver: 7.6.2
-
-  eslint-plugin-prettier@5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5):
-    dependencies:
-      eslint: 8.57.0
-      prettier: 3.2.5
-      prettier-linter-helpers: 1.0.0
-      synckit: 0.8.8
-    optionalDependencies:
-      '@types/eslint': 8.56.10
-      eslint-config-prettier: 9.1.0(eslint@8.57.0)
+  eslint-plugin-n@17.21.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
+    dependencies:
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      enhanced-resolve: 5.18.2
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-plugin-es-x: 7.8.0(eslint@9.30.1(jiti@2.4.2))
+      get-tsconfig: 4.10.1
+      globals: 15.15.0
+      ignore: 5.3.2
+      minimatch: 9.0.5
+      semver: 7.7.2
+      ts-declaration-location: 1.0.7(typescript@5.8.3)
+    transitivePeerDependencies:
+      - typescript
 
-  eslint-plugin-promise@6.1.1(eslint@8.57.0):
+  eslint-plugin-perfectionist@4.15.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
     dependencies:
-      eslint: 8.57.0
+      '@typescript-eslint/types': 8.35.1
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint: 9.30.1(jiti@2.4.2)
+      natural-orderby: 5.0.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
 
-  eslint-plugin-simple-import-sort@12.1.0(eslint@8.57.0):
+  eslint-plugin-promise@7.2.1(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      eslint: 8.57.0
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      eslint: 9.30.1(jiti@2.4.2)
 
-  eslint-plugin-tsdoc@0.2.17:
+  eslint-plugin-react@7.37.5(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      '@microsoft/tsdoc': 0.14.2
-      '@microsoft/tsdoc-config': 0.16.2
+      array-includes: 3.1.9
+      array.prototype.findlast: 1.2.5
+      array.prototype.flatmap: 1.3.3
+      array.prototype.tosorted: 1.1.4
+      doctrine: 2.1.0
+      es-iterator-helpers: 1.2.1
+      eslint: 9.30.1(jiti@2.4.2)
+      estraverse: 5.3.0
+      hasown: 2.0.2
+      jsx-ast-utils: 3.3.5
+      minimatch: 3.1.2
+      object.entries: 1.1.9
+      object.fromentries: 2.0.8
+      object.values: 1.2.1
+      prop-types: 15.8.1
+      resolve: 2.0.0-next.5
+      semver: 7.7.2
+      string.prototype.matchall: 4.0.12
+      string.prototype.repeat: 1.0.0
 
-  eslint-plugin-vue@9.26.0(eslint@8.57.0):
+  eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.30.1(jiti@2.4.2))):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
-      eslint: 8.57.0
-      globals: 13.24.0
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      eslint: 9.30.1(jiti@2.4.2)
       natural-compare: 1.4.0
       nth-check: 2.1.1
-      postcss-selector-parser: 6.0.16
-      semver: 7.6.2
-      vue-eslint-parser: 9.4.2(eslint@8.57.0)
+      postcss-selector-parser: 6.1.2
+      semver: 7.7.2
+      vue-eslint-parser: 10.2.0(eslint@9.30.1(jiti@2.4.2))
       xml-name-validator: 4.0.0
-    transitivePeerDependencies:
-      - supports-color
+    optionalDependencies:
+      '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
 
-  eslint-scope@7.2.2:
+  eslint-scope@8.4.0:
     dependencies:
       esrecurse: 4.3.0
       estraverse: 5.3.0
 
   eslint-visitor-keys@3.4.3: {}
 
-  eslint@8.57.0:
+  eslint-visitor-keys@4.2.1: {}
+
+  eslint@9.30.1(jiti@2.4.2):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
-      '@eslint-community/regexpp': 4.10.0
-      '@eslint/eslintrc': 2.1.4
-      '@eslint/js': 8.57.0
-      '@humanwhocodes/config-array': 0.11.14
+      '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2))
+      '@eslint-community/regexpp': 4.12.1
+      '@eslint/config-array': 0.21.0
+      '@eslint/config-helpers': 0.3.0
+      '@eslint/core': 0.14.0
+      '@eslint/eslintrc': 3.3.1
+      '@eslint/js': 9.30.1
+      '@eslint/plugin-kit': 0.3.3
+      '@humanfs/node': 0.16.6
       '@humanwhocodes/module-importer': 1.0.1
-      '@nodelib/fs.walk': 1.2.8
-      '@ungap/structured-clone': 1.2.0
+      '@humanwhocodes/retry': 0.4.3
+      '@types/estree': 1.0.8
+      '@types/json-schema': 7.0.15
       ajv: 6.12.6
       chalk: 4.1.2
-      cross-spawn: 7.0.3
-      debug: 4.3.4
-      doctrine: 3.0.0
+      cross-spawn: 7.0.6
+      debug: 4.4.1
       escape-string-regexp: 4.0.0
-      eslint-scope: 7.2.2
-      eslint-visitor-keys: 3.4.3
-      espree: 9.6.1
-      esquery: 1.5.0
+      eslint-scope: 8.4.0
+      eslint-visitor-keys: 4.2.1
+      espree: 10.4.0
+      esquery: 1.6.0
       esutils: 2.0.3
       fast-deep-equal: 3.1.3
-      file-entry-cache: 6.0.1
+      file-entry-cache: 8.0.0
       find-up: 5.0.0
       glob-parent: 6.0.2
-      globals: 13.24.0
-      graphemer: 1.4.0
-      ignore: 5.3.1
+      ignore: 5.3.2
       imurmurhash: 0.1.4
       is-glob: 4.0.3
-      is-path-inside: 3.0.3
-      js-yaml: 4.1.0
       json-stable-stringify-without-jsonify: 1.0.1
-      levn: 0.4.1
       lodash.merge: 4.6.2
       minimatch: 3.1.2
       natural-compare: 1.4.0
       optionator: 0.9.4
-      strip-ansi: 6.0.1
-      text-table: 0.2.0
+    optionalDependencies:
+      jiti: 2.4.2
     transitivePeerDependencies:
       - supports-color
 
@@ -9450,17 +9484,17 @@ snapshots:
       d: 1.0.2
       es5-ext: 0.10.64
       event-emitter: 0.3.5
-      type: 2.7.2
+      type: 2.7.3
 
-  espree@9.6.1:
+  espree@10.4.0:
     dependencies:
-      acorn: 8.11.3
-      acorn-jsx: 5.3.2(acorn@8.11.3)
-      eslint-visitor-keys: 3.4.3
+      acorn: 8.15.0
+      acorn-jsx: 5.3.2(acorn@8.15.0)
+      eslint-visitor-keys: 4.2.1
 
   esprima@4.0.1: {}
 
-  esquery@1.5.0:
+  esquery@1.6.0:
     dependencies:
       estraverse: 5.3.0
 
@@ -9480,7 +9514,7 @@ snapshots:
 
   estree-walker@3.0.3:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.8
 
   esutils@2.0.3: {}
 
@@ -9504,7 +9538,7 @@ snapshots:
 
   execa@4.1.0:
     dependencies:
-      cross-spawn: 7.0.3
+      cross-spawn: 7.0.6
       get-stream: 5.2.0
       human-signals: 1.1.1
       is-stream: 2.0.1
@@ -9514,47 +9548,17 @@ snapshots:
       signal-exit: 3.0.7
       strip-final-newline: 2.0.0
 
-  execa@5.1.1:
-    dependencies:
-      cross-spawn: 7.0.3
-      get-stream: 6.0.1
-      human-signals: 2.1.0
-      is-stream: 2.0.1
-      merge-stream: 2.0.0
-      npm-run-path: 4.0.1
-      onetime: 5.1.2
-      signal-exit: 3.0.7
-      strip-final-newline: 2.0.0
-
-  execa@8.0.1:
-    dependencies:
-      cross-spawn: 7.0.3
-      get-stream: 8.0.1
-      human-signals: 5.0.0
-      is-stream: 3.0.0
-      merge-stream: 2.0.0
-      npm-run-path: 5.3.0
-      onetime: 6.0.0
-      signal-exit: 4.1.0
-      strip-final-newline: 3.0.0
-
   execspawn@1.0.1:
     dependencies:
       util-extend: 1.0.3
 
   expand-template@2.0.3: {}
 
-  expect@29.7.0:
-    dependencies:
-      '@jest/expect-utils': 29.7.0
-      jest-get-type: 29.6.3
-      jest-matcher-utils: 29.7.0
-      jest-message-util: 29.7.0
-      jest-util: 29.7.0
+  expect-type@1.2.1: {}
 
   ext@1.7.0:
     dependencies:
-      type: 2.7.2
+      type: 2.7.3
 
   extend@3.0.2: {}
 
@@ -9568,15 +9572,15 @@ snapshots:
 
   fast-deep-equal@3.1.3: {}
 
-  fast-diff@1.3.0: {}
+  fast-equals@5.2.2: {}
 
-  fast-glob@3.3.2:
+  fast-glob@3.3.3:
     dependencies:
       '@nodelib/fs.stat': 2.0.5
       '@nodelib/fs.walk': 1.2.8
       glob-parent: 5.1.2
       merge2: 1.4.1
-      micromatch: 4.0.5
+      micromatch: 4.0.8
 
   fast-json-stable-stringify@2.1.0: {}
 
@@ -9584,26 +9588,27 @@ snapshots:
 
   fast-safe-stringify@2.1.1: {}
 
-  fastq@1.17.1:
+  fast-uri@3.0.6: {}
+
+  fastq@1.19.1:
     dependencies:
-      reusify: 1.0.4
+      reusify: 1.1.0
 
-  fecha@4.2.3: {}
+  fdir@6.4.6(picomatch@4.0.2):
+    optionalDependencies:
+      picomatch: 4.0.2
 
-  fetch-blob@3.2.0:
-    dependencies:
-      node-domexception: 1.0.0
-      web-streams-polyfill: 3.3.3
+  fecha@4.2.3: {}
 
-  figlet@1.7.0: {}
+  figlet@1.8.1: {}
 
   figures@2.0.0:
     dependencies:
       escape-string-regexp: 1.0.5
 
-  file-entry-cache@6.0.1:
+  file-entry-cache@8.0.0:
     dependencies:
-      flat-cache: 3.2.0
+      flat-cache: 4.0.1
 
   file-stream-rotator@0.6.1:
     dependencies:
@@ -9615,15 +9620,14 @@ snapshots:
     dependencies:
       to-regex-range: 5.0.1
 
-  finalhandler@1.2.0:
+  finalhandler@2.1.0:
     dependencies:
-      debug: 2.6.9
-      encodeurl: 1.0.2
+      debug: 4.4.1
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       on-finished: 2.4.1
       parseurl: 1.3.3
-      statuses: 2.0.1
-      unpipe: 1.0.0
+      statuses: 2.0.2
     transitivePeerDependencies:
       - supports-color
 
@@ -9646,25 +9650,24 @@ snapshots:
     dependencies:
       sinusoidal-decimal: 1.0.0
 
-  flat-cache@3.2.0:
+  flat-cache@4.0.1:
     dependencies:
-      flatted: 3.3.1
+      flatted: 3.3.3
       keyv: 4.5.4
-      rimraf: 3.0.2
 
   flatstr@1.0.12: {}
 
-  flatted@3.3.1: {}
+  flatted@3.3.3: {}
 
   fn.name@1.1.0: {}
 
-  for-each@0.3.3:
+  for-each@0.3.5:
     dependencies:
       is-callable: 1.2.7
 
-  foreground-child@3.1.1:
+  foreground-child@3.3.1:
     dependencies:
-      cross-spawn: 7.0.3
+      cross-spawn: 7.0.6
       signal-exit: 4.1.0
 
   forever-agent@0.6.1: {}
@@ -9677,17 +9680,15 @@ snapshots:
       combined-stream: 1.0.8
       mime-types: 2.1.35
 
-  form-data@4.0.0:
+  form-data@4.0.3:
     dependencies:
       asynckit: 0.4.0
       combined-stream: 1.0.8
+      es-set-tostringtag: 2.1.0
+      hasown: 2.0.2
       mime-types: 2.1.35
 
-  formdata-polyfill@4.0.10:
-    dependencies:
-      fetch-blob: 3.2.0
-
-  fresh@0.5.2: {}
+  fresh@2.0.0: {}
 
   from2-string@1.1.0:
     dependencies:
@@ -9706,7 +9707,7 @@ snapshots:
       jsonfile: 6.1.0
       universalify: 2.0.1
 
-  fs-extra@11.2.0:
+  fs-extra@11.3.0:
     dependencies:
       graceful-fs: 4.2.11
       jsonfile: 6.1.0
@@ -9723,12 +9724,14 @@ snapshots:
 
   function-bind@1.1.2: {}
 
-  function.prototype.name@1.1.6:
+  function.prototype.name@1.1.8:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      es-abstract: 1.23.3
       functions-have-names: 1.2.3
+      hasown: 2.0.2
+      is-callable: 1.2.7
 
   functions-have-names@1.2.3: {}
 
@@ -9752,53 +9755,52 @@ snapshots:
     dependencies:
       is-property: 1.0.2
 
+  gensequence@7.0.0: {}
+
   gensync@1.0.0-beta.2: {}
 
   get-assigned-identifiers@1.2.0: {}
 
   get-caller-file@2.0.5: {}
 
-  get-east-asian-width@1.2.0: {}
+  get-east-asian-width@1.3.0: {}
 
-  get-func-name@2.0.2: {}
-
-  get-intrinsic@1.2.4:
+  get-intrinsic@1.3.0:
     dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
       es-errors: 1.3.0
+      es-object-atoms: 1.1.1
       function-bind: 1.1.2
-      has-proto: 1.0.3
-      has-symbols: 1.0.3
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-symbols: 1.1.0
       hasown: 2.0.2
+      math-intrinsics: 1.1.0
 
   get-package-type@0.1.0: {}
 
+  get-proto@1.0.1:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-object-atoms: 1.1.1
+
   get-stream@5.2.0:
     dependencies:
-      pump: 3.0.0
+      pump: 3.0.3
 
   get-stream@6.0.1: {}
 
-  get-stream@8.0.1: {}
-
-  get-symbol-description@1.0.2:
+  get-symbol-description@1.1.0:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      get-intrinsic: 1.2.4
+      get-intrinsic: 1.3.0
 
-  get-tsconfig@4.7.5:
+  get-tsconfig@4.10.1:
     dependencies:
       resolve-pkg-maps: 1.0.0
 
-  get-uri@6.0.3:
-    dependencies:
-      basic-ftp: 5.0.5
-      data-uri-to-buffer: 6.0.2
-      debug: 4.3.4
-      fs-extra: 11.2.0
-    transitivePeerDependencies:
-      - supports-color
-
   getopts@2.3.0: {}
 
   getpass@0.1.7:
@@ -9811,15 +9813,6 @@ snapshots:
       meow: 12.1.1
       split2: 4.2.0
 
-  git-up@7.0.0:
-    dependencies:
-      is-ssh: 1.4.0
-      parse-url: 8.1.0
-
-  git-url-parse@14.0.0:
-    dependencies:
-      git-up: 7.0.0
-
   github-from-package@0.0.0: {}
 
   glob-parent@5.1.2:
@@ -9830,14 +9823,24 @@ snapshots:
     dependencies:
       is-glob: 4.0.3
 
-  glob@10.3.15:
+  glob@10.4.5:
     dependencies:
-      foreground-child: 3.1.1
-      jackspeak: 2.3.6
-      minimatch: 9.0.4
-      minipass: 7.1.1
+      foreground-child: 3.3.1
+      jackspeak: 3.4.3
+      minimatch: 9.0.5
+      minipass: 7.1.2
+      package-json-from-dist: 1.0.1
       path-scurry: 1.11.1
 
+  glob@11.0.3:
+    dependencies:
+      foreground-child: 3.3.1
+      jackspeak: 4.1.1
+      minimatch: 10.0.3
+      minipass: 7.1.2
+      package-json-from-dist: 1.0.1
+      path-scurry: 2.0.0
+
   glob@7.2.3:
     dependencies:
       fs.realpath: 1.0.0
@@ -9855,40 +9858,25 @@ snapshots:
     dependencies:
       ini: 2.0.0
 
-  globals@11.12.0: {}
-
-  globals@13.24.0:
-    dependencies:
-      type-fest: 0.20.2
+  globals@14.0.0: {}
 
-  globals@15.3.0: {}
+  globals@15.15.0: {}
 
   globalthis@1.0.4:
     dependencies:
       define-properties: 1.2.1
-      gopd: 1.0.1
+      gopd: 1.2.0
 
   globby@11.1.0:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.3.2
-      ignore: 5.3.1
+      fast-glob: 3.3.3
+      ignore: 5.3.2
       merge2: 1.4.1
       slash: 3.0.0
 
-  globby@14.0.1:
-    dependencies:
-      '@sindresorhus/merge-streams': 2.3.0
-      fast-glob: 3.3.2
-      ignore: 5.3.1
-      path-type: 5.0.0
-      slash: 5.1.0
-      unicorn-magic: 0.1.0
-
-  gopd@1.0.1:
-    dependencies:
-      get-intrinsic: 1.2.4
+  gopd@1.2.0: {}
 
   got@12.6.1:
     dependencies:
@@ -9904,21 +9892,10 @@ snapshots:
       p-cancelable: 3.0.0
       responselike: 3.0.0
 
-  graceful-fs@4.2.10: {}
-
   graceful-fs@4.2.11: {}
 
   graphemer@1.4.0: {}
 
-  handlebars@4.7.8:
-    dependencies:
-      minimist: 1.2.8
-      neo-async: 2.6.2
-      source-map: 0.6.1
-      wordwrap: 1.0.0
-    optionalDependencies:
-      uglify-js: 3.17.4
-
   har-schema@2.0.0: {}
 
   har-validator@5.1.5:
@@ -9932,23 +9909,27 @@ snapshots:
 
   has-async-hooks@1.0.0: {}
 
-  has-bigints@1.0.2: {}
+  has-bigints@1.1.0: {}
 
   has-flag@3.0.0: {}
 
   has-flag@4.0.0: {}
 
+  has-own-prop@2.0.0: {}
+
   has-property-descriptors@1.0.2:
     dependencies:
-      es-define-property: 1.0.0
+      es-define-property: 1.0.1
 
-  has-proto@1.0.3: {}
+  has-proto@1.2.0:
+    dependencies:
+      dunder-proto: 1.0.1
 
-  has-symbols@1.0.3: {}
+  has-symbols@1.1.0: {}
 
   has-tostringtag@1.0.2:
     dependencies:
-      has-symbols: 1.0.3
+      has-symbols: 1.1.0
 
   has-unicode@2.0.1: {}
 
@@ -9956,15 +9937,13 @@ snapshots:
 
   has@1.0.4: {}
 
-  hash-base@3.0.4:
+  hash-base@2.0.2:
     dependencies:
       inherits: 2.0.4
-      safe-buffer: 5.2.1
 
-  hash-base@3.1.0:
+  hash-base@3.0.5:
     dependencies:
       inherits: 2.0.4
-      readable-stream: 3.6.2
       safe-buffer: 5.2.1
 
   hash.js@1.1.7:
@@ -9976,7 +9955,7 @@ snapshots:
     dependencies:
       function-bind: 1.1.2
 
-  hdr-histogram-js@3.0.0:
+  hdr-histogram-js@3.0.1:
     dependencies:
       '@assemblyscript/loader': 0.19.23
       base64-js: 1.5.1
@@ -9993,7 +9972,7 @@ snapshots:
       ndarray-determinant: 1.0.0
       ndarray-inv: 0.2.0
       seedrandom: 3.0.5
-      semver: 7.6.2
+      semver: 7.7.2
 
   hmac-drbg@1.0.1:
     dependencies:
@@ -10009,11 +9988,9 @@ snapshots:
 
   html-escaper@2.0.2: {}
 
-  html-tags@3.3.1: {}
-
   htmlescape@1.1.1: {}
 
-  http-cache-semantics@4.1.1: {}
+  http-cache-semantics@4.2.0: {}
 
   http-errors@2.0.0:
     dependencies:
@@ -10023,21 +10000,21 @@ snapshots:
       statuses: 2.0.1
       toidentifier: 1.0.1
 
-  http-parser-js@0.5.8: {}
+  http-parser-js@0.5.10: {}
 
   http-proxy-agent@4.0.1:
     dependencies:
       '@tootallnate/once': 1.1.2
       agent-base: 6.0.2
-      debug: 4.3.4
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
     optional: true
 
   http-proxy-agent@7.0.2:
     dependencies:
-      agent-base: 7.1.1
-      debug: 4.3.4
+      agent-base: 7.1.3
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
 
@@ -10059,32 +10036,28 @@ snapshots:
   https-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
     optional: true
 
-  https-proxy-agent@7.0.4:
+  https-proxy-agent@7.0.6:
     dependencies:
-      agent-base: 7.1.1
-      debug: 4.3.4
+      agent-base: 7.1.3
+      debug: 4.4.1
     transitivePeerDependencies:
       - supports-color
 
   human-signals@1.1.1: {}
 
-  human-signals@2.1.0: {}
-
-  human-signals@5.0.0: {}
-
   humanize-ms@1.2.1:
     dependencies:
       ms: 2.1.3
     optional: true
 
-  husky@9.0.11: {}
+  husky@9.1.7: {}
 
-  hyperid@3.2.0:
+  hyperid@3.3.0:
     dependencies:
       buffer: 5.7.1
       uuid: 9.0.1
@@ -10106,17 +10079,17 @@ snapshots:
 
   ieee754@1.2.1: {}
 
-  ignore@5.3.1: {}
+  ignore@5.3.2: {}
+
+  ignore@7.0.5: {}
 
-  import-fresh@3.3.0:
+  import-fresh@3.3.1:
     dependencies:
       parent-module: 1.0.1
       resolve-from: 4.0.0
 
   import-lazy@2.1.0: {}
 
-  import-lazy@4.0.0: {}
-
   import-meta-resolve@4.1.0: {}
 
   imurmurhash@0.1.4: {}
@@ -10141,8 +10114,6 @@ snapshots:
 
   ini@4.1.1: {}
 
-  ini@4.1.2: {}
-
   inline-source-map@0.6.3:
     dependencies:
       source-map: 0.5.7
@@ -10163,24 +10134,6 @@ snapshots:
       strip-ansi: 5.2.0
       through: 2.3.8
 
-  inquirer@9.2.22:
-    dependencies:
-      '@inquirer/figures': 1.0.2
-      '@ljharb/through': 2.3.13
-      ansi-escapes: 4.3.2
-      chalk: 5.3.0
-      cli-cursor: 3.1.0
-      cli-width: 4.1.0
-      external-editor: 3.1.0
-      lodash: 4.17.21
-      mute-stream: 1.0.0
-      ora: 5.4.1
-      run-async: 3.0.0
-      rxjs: 7.8.1
-      string-width: 4.2.3
-      strip-ansi: 6.0.1
-      wrap-ansi: 6.2.0
-
   insert-module-globals@7.2.1:
     dependencies:
       JSONStream: 1.3.5
@@ -10206,16 +10159,14 @@ snapshots:
       tough-cookie: 4.1.4
       uuid: 9.0.1
 
-  internal-slot@1.0.7:
+  internal-slot@1.1.0:
     dependencies:
       es-errors: 1.3.0
       hasown: 2.0.2
-      side-channel: 1.0.6
+      side-channel: 1.1.0
 
   internmap@1.0.1: {}
 
-  interpret@1.4.0: {}
-
   interpret@2.2.0: {}
 
   iota-array@1.0.0: {}
@@ -10224,26 +10175,36 @@ snapshots:
     dependencies:
       jsbn: 1.1.0
       sprintf-js: 1.1.3
+    optional: true
 
   is-any-array@2.0.1: {}
 
-  is-arguments@1.1.1:
+  is-arguments@1.2.0:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
-  is-array-buffer@3.0.4:
+  is-array-buffer@3.0.5:
     dependencies:
-      call-bind: 1.0.7
-      get-intrinsic: 1.2.4
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
 
   is-arrayish@0.2.1: {}
 
   is-arrayish@0.3.2: {}
 
-  is-bigint@1.0.4:
+  is-async-function@2.1.1:
     dependencies:
-      has-bigints: 1.0.2
+      async-function: 1.0.0
+      call-bound: 1.0.4
+      get-proto: 1.0.1
+      has-tostringtag: 1.0.2
+      safe-regex-test: 1.1.0
+
+  is-bigint@1.1.0:
+    dependencies:
+      has-bigints: 1.1.0
 
   is-binary-path@2.1.0:
     dependencies:
@@ -10251,18 +10212,18 @@ snapshots:
 
   is-boolean-attribute@0.0.1: {}
 
-  is-boolean-object@1.1.2:
+  is-boolean-object@1.2.2:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-buffer@1.1.6: {}
 
   is-buffer@2.0.5: {}
 
-  is-builtin-module@3.2.1:
+  is-bun-module@2.0.0:
     dependencies:
-      builtin-modules: 3.3.0
+      semver: 7.7.2
 
   is-callable@1.2.7: {}
 
@@ -10270,28 +10231,29 @@ snapshots:
     dependencies:
       ci-info: 2.0.0
 
-  is-ci@3.0.1:
-    dependencies:
-      ci-info: 3.9.0
-
-  is-core-module@2.13.1:
+  is-core-module@2.16.1:
     dependencies:
       hasown: 2.0.2
 
-  is-data-view@1.0.1:
+  is-data-view@1.0.2:
     dependencies:
-      is-typed-array: 1.1.13
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+      is-typed-array: 1.1.15
 
-  is-date-object@1.0.5:
+  is-date-object@1.1.0:
     dependencies:
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-docker@2.2.1: {}
 
-  is-docker@3.0.0: {}
-
   is-extglob@2.1.1: {}
 
+  is-finalizationregistry@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+
   is-fullwidth-code-point@1.0.0:
     dependencies:
       number-is-nan: 1.0.1
@@ -10304,22 +10266,19 @@ snapshots:
 
   is-fullwidth-code-point@5.0.0:
     dependencies:
-      get-east-asian-width: 1.2.0
+      get-east-asian-width: 1.3.0
 
-  is-generator-function@1.0.10:
+  is-generator-function@1.1.0:
     dependencies:
+      call-bound: 1.0.4
+      get-proto: 1.0.1
       has-tostringtag: 1.0.2
+      safe-regex-test: 1.1.0
 
   is-glob@4.0.3:
     dependencies:
       is-extglob: 2.1.1
 
-  is-in-ci@0.1.0: {}
-
-  is-inside-container@1.0.0:
-    dependencies:
-      is-docker: 3.0.0
-
   is-installed-globally@0.4.0:
     dependencies:
       global-dirs: 3.0.1
@@ -10327,8 +10286,6 @@ snapshots:
 
   is-interactive@1.0.0: {}
 
-  is-interactive@2.0.0: {}
-
   is-lambda@1.0.1:
     optional: true
 
@@ -10338,10 +10295,9 @@ snapshots:
 
   is-npm@5.0.0: {}
 
-  is-npm@6.0.0: {}
-
-  is-number-object@1.0.7:
+  is-number-object@1.1.1:
     dependencies:
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
   is-number@7.0.0: {}
@@ -10356,52 +10312,54 @@ snapshots:
 
   is-property@1.0.2: {}
 
-  is-regex@1.1.4:
+  is-regex@1.2.1:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
+      gopd: 1.2.0
       has-tostringtag: 1.0.2
+      hasown: 2.0.2
 
   is-set@2.0.3: {}
 
-  is-shared-array-buffer@1.0.3:
-    dependencies:
-      call-bind: 1.0.7
-
-  is-ssh@1.4.0:
+  is-shared-array-buffer@1.0.4:
     dependencies:
-      protocols: 2.0.1
+      call-bound: 1.0.4
 
   is-stream@2.0.1: {}
 
-  is-stream@3.0.0: {}
-
-  is-string@1.0.7:
+  is-string@1.1.1:
     dependencies:
+      call-bound: 1.0.4
       has-tostringtag: 1.0.2
 
-  is-symbol@1.0.4:
+  is-symbol@1.1.1:
     dependencies:
-      has-symbols: 1.0.3
+      call-bound: 1.0.4
+      has-symbols: 1.1.0
+      safe-regex-test: 1.1.0
 
   is-text-path@2.0.0:
     dependencies:
       text-extensions: 2.4.0
 
-  is-typed-array@1.1.13:
+  is-typed-array@1.1.15:
     dependencies:
-      which-typed-array: 1.1.15
+      which-typed-array: 1.1.19
 
   is-typedarray@1.0.0: {}
 
   is-unicode-supported@0.1.0: {}
 
-  is-unicode-supported@1.3.0: {}
+  is-weakmap@2.0.2: {}
 
-  is-unicode-supported@2.0.0: {}
+  is-weakref@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
 
-  is-weakref@1.0.2:
+  is-weakset@2.0.4:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
 
   is-wsl@1.1.0: {}
 
@@ -10409,10 +10367,6 @@ snapshots:
     dependencies:
       is-docker: 2.2.1
 
-  is-wsl@3.1.0:
-    dependencies:
-      is-inside-container: 1.0.0
-
   is-yarn-global@0.3.0: {}
 
   isarray@1.0.0: {}
@@ -10423,14 +10377,6 @@ snapshots:
 
   isstream@0.1.2: {}
 
-  issue-parser@7.0.0:
-    dependencies:
-      lodash.capitalize: 4.2.1
-      lodash.escaperegexp: 4.1.2
-      lodash.isplainobject: 4.0.6
-      lodash.isstring: 4.0.1
-      lodash.uniqby: 4.7.0
-
   istanbul-lib-coverage@3.2.2: {}
 
   istanbul-lib-report@3.0.1:
@@ -10439,10 +10385,10 @@ snapshots:
       make-dir: 4.0.0
       supports-color: 7.2.0
 
-  istanbul-lib-source-maps@5.0.4:
+  istanbul-lib-source-maps@5.0.6:
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.25
-      debug: 4.3.4
+      '@jridgewell/trace-mapping': 0.3.29
+      debug: 4.4.1
       istanbul-lib-coverage: 3.2.2
     transitivePeerDependencies:
       - supports-color
@@ -10452,65 +10398,32 @@ snapshots:
       html-escaper: 2.0.2
       istanbul-lib-report: 3.0.1
 
-  iterate-iterator@1.0.2: {}
-
-  iterate-value@1.0.2:
+  iterator.prototype@1.1.5:
     dependencies:
-      es-get-iterator: 1.1.3
-      iterate-iterator: 1.0.2
+      define-data-property: 1.1.4
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      has-symbols: 1.1.0
+      set-function-name: 2.0.2
 
-  jackspeak@2.3.6:
+  jackspeak@3.4.3:
     dependencies:
       '@isaacs/cliui': 8.0.2
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0
 
-  jest-diff@29.7.0:
-    dependencies:
-      chalk: 4.1.2
-      diff-sequences: 29.6.3
-      jest-get-type: 29.6.3
-      pretty-format: 29.7.0
-
-  jest-get-type@29.6.3: {}
-
-  jest-matcher-utils@29.7.0:
-    dependencies:
-      chalk: 4.1.2
-      jest-diff: 29.7.0
-      jest-get-type: 29.6.3
-      pretty-format: 29.7.0
-
-  jest-message-util@29.7.0:
-    dependencies:
-      '@babel/code-frame': 7.24.2
-      '@jest/types': 29.6.3
-      '@types/stack-utils': 2.0.3
-      chalk: 4.1.2
-      graceful-fs: 4.2.11
-      micromatch: 4.0.5
-      pretty-format: 29.7.0
-      slash: 3.0.0
-      stack-utils: 2.0.6
-
-  jest-util@29.7.0:
+  jackspeak@4.1.1:
     dependencies:
-      '@jest/types': 29.6.3
-      '@types/node': 20.12.12
-      chalk: 4.1.2
-      ci-info: 3.9.0
-      graceful-fs: 4.2.11
-      picomatch: 2.3.1
-
-  jiti@1.21.0: {}
+      '@isaacs/cliui': 8.0.2
 
-  jju@1.4.0: {}
+  jiti@2.4.2: {}
 
-  js-beautify@1.15.1:
+  js-beautify@1.15.4:
     dependencies:
       config-chain: 1.1.13
       editorconfig: 1.0.4
-      glob: 10.3.15
+      glob: 10.4.5
       js-cookie: 3.0.5
       nopt: 7.2.1
 
@@ -10518,7 +10431,7 @@ snapshots:
 
   js-tokens@4.0.0: {}
 
-  js-tokens@9.0.0: {}
+  js-tokens@9.0.1: {}
 
   js-yaml@4.1.0:
     dependencies:
@@ -10526,23 +10439,23 @@ snapshots:
 
   jsbn@0.1.1: {}
 
-  jsbn@1.1.0: {}
+  jsbn@1.1.0:
+    optional: true
 
-  jsdoc-type-pratt-parser@4.0.0: {}
+  jsdoc-type-pratt-parser@4.1.0: {}
 
-  jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4):
+  jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5):
     dependencies:
-      cssstyle: 4.0.1
+      cssstyle: 4.6.0
       data-urls: 5.0.0
-      decimal.js: 10.4.3
-      form-data: 4.0.0
+      decimal.js: 10.5.0
       html-encoding-sniffer: 4.0.0
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.4
+      https-proxy-agent: 7.0.6
       is-potential-custom-element-name: 1.0.1
-      nwsapi: 2.2.10
-      parse5: 7.1.2
-      rrweb-cssom: 0.6.0
+      nwsapi: 2.2.20
+      parse5: 7.3.0
+      rrweb-cssom: 0.8.0
       saxes: 6.0.0
       symbol-tree: 3.2.4
       tough-cookie: 4.1.4
@@ -10550,15 +10463,15 @@ snapshots:
       webidl-conversions: 7.0.0
       whatwg-encoding: 3.1.1
       whatwg-mimetype: 4.0.0
-      whatwg-url: 14.0.0
-      ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      whatwg-url: 14.2.0
+      ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
       xml-name-validator: 5.0.0
     transitivePeerDependencies:
       - bufferutil
       - supports-color
       - utf-8-validate
 
-  jsesc@2.5.2: {}
+  jsesc@3.1.0: {}
 
   json-buffer@3.0.1: {}
 
@@ -10576,10 +10489,6 @@ snapshots:
 
   json-stringify-safe@5.0.1: {}
 
-  json5@1.0.2:
-    dependencies:
-      minimist: 1.2.8
-
   json5@2.2.3: {}
 
   jsonfile@6.1.0:
@@ -10603,6 +10512,13 @@ snapshots:
       json-schema: 0.4.0
       verror: 1.10.0
 
+  jsx-ast-utils@3.3.5:
+    dependencies:
+      array-includes: 3.1.9
+      array.prototype.flat: 1.3.3
+      object.assign: 4.1.7
+      object.values: 1.2.1
+
   keyv@4.5.4:
     dependencies:
       json-buffer: 3.0.1
@@ -10612,7 +10528,7 @@ snapshots:
       colorette: 2.0.19
       commander: 10.0.1
       debug: 4.3.4
-      escalade: 3.1.2
+      escalade: 3.2.0
       esm: 3.2.25
       get-package-type: 0.1.0
       getopts: 2.3.0
@@ -10639,10 +10555,6 @@ snapshots:
     dependencies:
       package-json: 6.5.0
 
-  latest-version@7.0.0:
-    dependencies:
-      package-json: 8.1.1
-
   leven@2.1.0: {}
 
   levn@0.3.0:
@@ -10655,39 +10567,34 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  lilconfig@3.0.0: {}
+  lilconfig@3.1.3: {}
 
   lines-and-columns@1.2.4: {}
 
-  lint-staged@15.2.2:
+  lint-staged@16.1.2:
     dependencies:
-      chalk: 5.3.0
-      commander: 11.1.0
-      debug: 4.3.4
-      execa: 8.0.1
-      lilconfig: 3.0.0
-      listr2: 8.0.1
-      micromatch: 4.0.5
+      chalk: 5.4.1
+      commander: 14.0.0
+      debug: 4.4.1
+      lilconfig: 3.1.3
+      listr2: 8.3.3
+      micromatch: 4.0.8
+      nano-spawn: 1.0.2
       pidtree: 0.6.0
       string-argv: 0.3.2
-      yaml: 2.3.4
+      yaml: 2.8.0
     transitivePeerDependencies:
       - supports-color
 
-  listr2@8.0.1:
+  listr2@8.3.3:
     dependencies:
       cli-truncate: 4.0.0
       colorette: 2.0.20
       eventemitter3: 5.0.1
-      log-update: 6.0.0
-      rfdc: 1.3.1
+      log-update: 6.1.0
+      rfdc: 1.4.1
       wrap-ansi: 9.0.0
 
-  local-pkg@0.5.0:
-    dependencies:
-      mlly: 1.7.0
-      pkg-types: 1.1.1
-
   locate-path@3.0.0:
     dependencies:
       p-locate: 3.0.0
@@ -10701,26 +10608,18 @@ snapshots:
     dependencies:
       p-locate: 6.0.0
 
-  lodash-es@4.17.21: {}
-
   lodash.camelcase@4.3.0: {}
 
-  lodash.capitalize@4.2.1: {}
-
   lodash.chunk@4.2.0: {}
 
   lodash.clonedeep@4.5.0: {}
 
   lodash.debounce@4.0.8: {}
 
-  lodash.escaperegexp@4.1.2: {}
-
   lodash.flatten@4.4.0: {}
 
   lodash.isplainobject@4.0.6: {}
 
-  lodash.isstring@4.0.1: {}
-
   lodash.kebabcase@4.1.1: {}
 
   lodash.memoize@3.0.4: {}
@@ -10735,8 +10634,6 @@ snapshots:
 
   lodash.uniq@4.5.0: {}
 
-  lodash.uniqby@4.7.0: {}
-
   lodash.upperfirst@4.3.1: {}
 
   lodash@4.17.21: {}
@@ -10746,26 +10643,21 @@ snapshots:
       chalk: 4.1.2
       is-unicode-supported: 0.1.0
 
-  log-symbols@6.0.0:
-    dependencies:
-      chalk: 5.3.0
-      is-unicode-supported: 1.3.0
-
-  log-update@6.0.0:
+  log-update@6.1.0:
     dependencies:
-      ansi-escapes: 6.2.1
-      cli-cursor: 4.0.0
+      ansi-escapes: 7.0.0
+      cli-cursor: 5.0.0
       slice-ansi: 7.1.0
       strip-ansi: 7.1.0
       wrap-ansi: 9.0.0
 
-  logform@2.6.0:
+  logform@2.7.0:
     dependencies:
       '@colors/colors': 1.6.0
       '@types/triple-beam': 1.3.5
       fecha: 4.2.3
       ms: 2.1.3
-      safe-stable-stringify: 2.4.3
+      safe-stable-stringify: 2.5.0
       triple-beam: 1.4.1
 
   long@4.0.0: {}
@@ -10774,15 +10666,15 @@ snapshots:
     dependencies:
       js-tokens: 4.0.0
 
-  loupe@2.3.7:
-    dependencies:
-      get-func-name: 2.0.2
+  loupe@3.1.4: {}
 
   lower-case@1.1.4: {}
 
   lowercase-keys@3.0.0: {}
 
-  lru-cache@10.2.2: {}
+  lru-cache@10.4.3: {}
+
+  lru-cache@11.1.0: {}
 
   lru-cache@5.1.1:
     dependencies:
@@ -10793,12 +10685,8 @@ snapshots:
       yallist: 4.0.0
     optional: true
 
-  lru-cache@7.18.3: {}
-
   macos-release@2.5.1: {}
 
-  macos-release@3.2.0: {}
-
   magic-string@0.23.2:
     dependencies:
       sourcemap-codec: 1.4.8
@@ -10807,31 +10695,31 @@ snapshots:
     dependencies:
       sourcemap-codec: 1.4.8
 
-  magic-string@0.30.10:
+  magic-string@0.30.17:
     dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.4
 
-  magicast@0.3.4:
+  magicast@0.3.5:
     dependencies:
-      '@babel/parser': 7.24.5
-      '@babel/types': 7.24.5
-      source-map-js: 1.2.0
+      '@babel/parser': 7.28.0
+      '@babel/types': 7.28.0
+      source-map-js: 1.2.1
 
   make-dir@3.1.0:
     dependencies:
-      semver: 7.6.2
+      semver: 7.7.2
 
   make-dir@4.0.0:
     dependencies:
-      semver: 7.6.2
+      semver: 7.7.2
 
   make-error@1.3.6: {}
 
   make-fetch-happen@9.1.0:
     dependencies:
-      agentkeepalive: 4.5.0
+      agentkeepalive: 4.6.0
       cacache: 15.3.0
-      http-cache-semantics: 4.1.1
+      http-cache-semantics: 4.2.0
       http-proxy-agent: 4.0.1
       https-proxy-agent: 5.0.1
       is-lambda: 1.0.1
@@ -10841,7 +10729,7 @@ snapshots:
       minipass-fetch: 1.4.1
       minipass-flush: 1.0.5
       minipass-pipeline: 1.2.4
-      negotiator: 0.6.3
+      negotiator: 0.6.4
       promise-retry: 2.0.1
       socks-proxy-agent: 6.2.1
       ssri: 8.0.1
@@ -10852,17 +10740,19 @@ snapshots:
 
   manage-path@2.0.0: {}
 
-  mariadb@3.3.0:
+  mariadb@3.4.2:
     dependencies:
-      '@types/geojson': 7946.0.14
-      '@types/node': 20.12.12
+      '@types/geojson': 7946.0.16
+      '@types/node': 22.16.0
       denque: 2.1.0
       iconv-lite: 0.6.3
-      lru-cache: 10.2.2
+      lru-cache: 10.4.3
+
+  math-intrinsics@1.1.0: {}
 
   md5.js@1.3.5:
     dependencies:
-      hash-base: 3.1.0
+      hash-base: 3.0.5
       inherits: 2.0.4
       safe-buffer: 5.2.1
 
@@ -10878,25 +10768,29 @@ snapshots:
 
   merge2@1.4.1: {}
 
-  micromatch@4.0.5:
+  micromatch@4.0.8:
     dependencies:
       braces: 3.0.3
       picomatch: 2.3.1
 
-  mikro-orm@6.2.7: {}
+  mikro-orm@6.4.16: {}
 
   miller-rabin@4.0.1:
     dependencies:
-      bn.js: 4.12.0
+      bn.js: 4.12.2
       brorand: 1.1.0
 
   mime-db@1.52.0: {}
 
+  mime-db@1.54.0: {}
+
   mime-types@2.1.35:
     dependencies:
       mime-db: 1.52.0
 
-  mime@1.6.0: {}
+  mime-types@3.0.1:
+    dependencies:
+      mime-db: 1.54.0
 
   mimic-fn@1.2.0: {}
 
@@ -10904,7 +10798,7 @@ snapshots:
 
   mimic-fn@3.1.0: {}
 
-  mimic-fn@4.0.0: {}
+  mimic-function@5.0.1: {}
 
   mimic-response@3.1.0: {}
 
@@ -10923,17 +10817,21 @@ snapshots:
 
   minimalistic-crypto-utils@1.0.1: {}
 
+  minimatch@10.0.3:
+    dependencies:
+      '@isaacs/brace-expansion': 5.0.0
+
   minimatch@3.1.2:
     dependencies:
-      brace-expansion: 1.1.11
+      brace-expansion: 1.1.12
 
   minimatch@9.0.1:
     dependencies:
-      brace-expansion: 2.0.1
+      brace-expansion: 2.0.2
 
-  minimatch@9.0.4:
+  minimatch@9.0.5:
     dependencies:
-      brace-expansion: 2.0.1
+      brace-expansion: 2.0.2
 
   minimist@1.2.8: {}
 
@@ -10972,17 +10870,16 @@ snapshots:
 
   minipass@5.0.0: {}
 
-  minipass@7.1.1: {}
+  minipass@7.1.2: {}
 
   minizlib@2.1.2:
     dependencies:
       minipass: 3.3.6
       yallist: 4.0.0
 
-  minizlib@3.0.1:
+  minizlib@3.0.2:
     dependencies:
-      minipass: 7.1.1
-      rimraf: 5.0.7
+      minipass: 7.1.2
 
   mkdirp-classic@0.5.3: {}
 
@@ -11028,16 +10925,9 @@ snapshots:
 
   ml-xsadd@2.0.0: {}
 
-  mlly@1.7.0:
+  mnemonist@0.40.3:
     dependencies:
-      acorn: 8.11.3
-      pathe: 1.1.2
-      pkg-types: 1.1.1
-      ufo: 1.5.3
-
-  mnemonist@0.40.0-rc1:
-    dependencies:
-      obliterator: 2.0.4
+      obliterator: 2.0.5
 
   module-deps@6.2.3:
     dependencies:
@@ -11051,7 +10941,7 @@ snapshots:
       inherits: 2.0.4
       parents: 1.0.1
       readable-stream: 2.3.8
-      resolve: 1.22.8
+      resolve: 1.22.10
       stream-combiner2: 1.1.1
       subarg: 1.0.0
       through2: 2.0.5
@@ -11059,22 +10949,20 @@ snapshots:
 
   moment@2.30.1: {}
 
-  mongodb-connection-string-url@3.0.1:
+  mongodb-connection-string-url@3.0.2:
     dependencies:
       '@types/whatwg-url': 11.0.5
-      whatwg-url: 13.0.0
+      whatwg-url: 14.2.0
 
-  mongodb@6.6.2(socks@2.8.3):
+  mongodb@6.17.0(socks@2.8.5):
     dependencies:
-      '@mongodb-js/saslprep': 1.1.7
-      bson: 6.7.0
-      mongodb-connection-string-url: 3.0.1
+      '@mongodb-js/saslprep': 1.3.0
+      bson: 6.10.4
+      mongodb-connection-string-url: 3.0.2
     optionalDependencies:
-      socks: 2.8.3
+      socks: 2.8.5
 
-  morphdom@2.7.2: {}
-
-  ms@2.0.0: {}
+  morphdom@2.7.5: {}
 
   ms@2.1.2: {}
 
@@ -11087,12 +10975,12 @@ snapshots:
 
   mute-stream@0.0.7: {}
 
-  mute-stream@1.0.0: {}
-
   mutexify@1.4.0:
     dependencies:
       queue-tick: 1.0.1
 
+  nano-spawn@1.0.2: {}
+
   nanoassert@1.1.0: {}
 
   nanobench@2.1.1:
@@ -11116,12 +11004,16 @@ snapshots:
       through2: 2.0.5
       transform-ast: 2.4.4
 
-  nanoid@3.3.7: {}
+  nanoid@3.3.11: {}
+
+  napi-build-utils@2.0.0: {}
 
-  napi-build-utils@1.0.2: {}
+  napi-postinstall@0.3.0: {}
 
   natural-compare@1.4.0: {}
 
+  natural-orderby@5.0.0: {}
+
   ndarray-blas-level1@1.1.3: {}
 
   ndarray-cholesky-factorization@1.0.2:
@@ -11163,16 +11055,29 @@ snapshots:
       iota-array: 1.0.0
       is-buffer: 1.1.6
 
-  negotiator@0.6.3:
+  negotiator@0.6.4:
     optional: true
 
-  neo-async@2.6.2: {}
-
-  netmask@2.0.2: {}
-
-  new-github-release-url@2.0.0:
+  neostandard@0.12.2(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
     dependencies:
-      type-fest: 2.19.0
+      '@humanwhocodes/gitignore-to-minimatch': 1.0.2
+      '@stylistic/eslint-plugin': 2.11.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2))
+      eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))
+      eslint-plugin-n: 17.21.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint-plugin-promise: 7.2.1(eslint@9.30.1(jiti@2.4.2))
+      eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.4.2))
+      find-up: 5.0.0
+      globals: 15.15.0
+      peowly: 1.3.2
+      typescript-eslint: 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+    transitivePeerDependencies:
+      - '@typescript-eslint/utils'
+      - eslint-import-resolver-node
+      - eslint-plugin-import
+      - supports-color
+      - typescript
 
   next-tick@1.1.0: {}
 
@@ -11180,13 +11085,11 @@ snapshots:
     dependencies:
       lower-case: 1.1.4
 
-  node-abi@3.62.0:
+  node-abi@3.75.0:
     dependencies:
-      semver: 7.6.2
+      semver: 7.7.2
 
-  node-addon-api@7.1.0: {}
-
-  node-domexception@1.0.0: {}
+  node-addon-api@7.1.1: {}
 
   node-fetch@2.6.13(encoding@0.1.13):
     dependencies:
@@ -11194,19 +11097,7 @@ snapshots:
     optionalDependencies:
       encoding: 0.1.13
 
-  node-fetch@2.7.0(encoding@0.1.13):
-    dependencies:
-      whatwg-url: 5.0.0
-    optionalDependencies:
-      encoding: 0.1.13
-
-  node-fetch@3.3.2:
-    dependencies:
-      data-uri-to-buffer: 4.0.1
-      fetch-blob: 3.2.0
-      formdata-polyfill: 4.0.10
-
-  node-gyp-build@4.8.1:
+  node-gyp-build@4.8.4:
     optional: true
 
   node-gyp@8.4.1:
@@ -11218,7 +11109,7 @@ snapshots:
       nopt: 5.0.0
       npmlog: 6.0.2
       rimraf: 3.0.2
-      semver: 7.6.2
+      semver: 7.7.2
       tar: 6.2.1
       which: 2.0.2
     transitivePeerDependencies:
@@ -11226,7 +11117,7 @@ snapshots:
       - supports-color
     optional: true
 
-  node-releases@2.0.14: {}
+  node-releases@2.0.19: {}
 
   nopt@5.0.0:
     dependencies:
@@ -11241,16 +11132,12 @@ snapshots:
 
   normalize-path@3.0.0: {}
 
-  normalize-url@8.0.1: {}
+  normalize-url@8.0.2: {}
 
   npm-run-path@4.0.1:
     dependencies:
       path-key: 3.1.1
 
-  npm-run-path@5.3.0:
-    dependencies:
-      path-key: 4.0.0
-
   npmlog@6.0.2:
     dependencies:
       are-we-there-yet: 3.0.1
@@ -11265,43 +11152,49 @@ snapshots:
 
   number-is-nan@1.0.1: {}
 
-  nwsapi@2.2.10: {}
+  nwsapi@2.2.20: {}
 
   oauth-sign@0.9.0: {}
 
+  object-assign@4.1.1: {}
+
   object-hash@3.0.0: {}
 
-  object-inspect@1.13.1: {}
+  object-inspect@1.13.4: {}
 
   object-keys@1.1.1: {}
 
-  object.assign@4.1.5:
+  object.assign@4.1.7:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      has-symbols: 1.0.3
+      es-object-atoms: 1.1.1
+      has-symbols: 1.1.0
       object-keys: 1.1.1
 
-  object.fromentries@2.0.8:
+  object.entries@1.1.9:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-object-atoms: 1.0.0
+      es-object-atoms: 1.1.1
 
-  object.groupby@1.0.3:
+  object.fromentries@2.0.8:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-abstract: 1.23.3
+      es-abstract: 1.24.0
+      es-object-atoms: 1.1.1
 
-  object.values@1.2.0:
+  object.values@1.2.1:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      es-object-atoms: 1.0.0
+      es-object-atoms: 1.1.1
 
-  obliterator@2.0.4: {}
+  obliterator@2.0.5: {}
 
   on-finished@2.4.1:
     dependencies:
@@ -11325,16 +11218,9 @@ snapshots:
     dependencies:
       mimic-fn: 2.1.0
 
-  onetime@6.0.0:
-    dependencies:
-      mimic-fn: 4.0.0
-
-  open@10.1.0:
+  onetime@7.0.0:
     dependencies:
-      default-browser: 5.2.1
-      define-lazy-prop: 3.0.0
-      is-inside-container: 1.0.0
-      is-wsl: 3.1.0
+      mimic-function: 5.0.1
 
   open@7.4.2:
     dependencies:
@@ -11375,18 +11261,6 @@ snapshots:
       strip-ansi: 6.0.1
       wcwidth: 1.0.1
 
-  ora@8.0.1:
-    dependencies:
-      chalk: 5.3.0
-      cli-cursor: 4.0.0
-      cli-spinners: 2.9.2
-      is-interactive: 2.0.0
-      is-unicode-supported: 2.0.0
-      log-symbols: 6.0.0
-      stdin-discarder: 0.2.2
-      string-width: 7.1.0
-      strip-ansi: 7.1.0
-
   os-browserify@0.3.0: {}
 
   os-name@4.0.1:
@@ -11394,13 +11268,14 @@ snapshots:
       macos-release: 2.5.1
       windows-release: 4.0.0
 
-  os-name@5.1.0:
-    dependencies:
-      macos-release: 3.2.0
-      windows-release: 5.1.1
-
   os-tmpdir@1.0.2: {}
 
+  own-keys@1.0.1:
+    dependencies:
+      get-intrinsic: 1.3.0
+      object-keys: 1.1.1
+      safe-push-apply: 1.0.0
+
   p-cancelable@3.0.0: {}
 
   p-limit@2.3.0:
@@ -11413,11 +11288,7 @@ snapshots:
 
   p-limit@4.0.0:
     dependencies:
-      yocto-queue: 1.0.0
-
-  p-limit@5.0.0:
-    dependencies:
-      yocto-queue: 1.0.0
+      yocto-queue: 1.2.1
 
   p-locate@3.0.0:
     dependencies:
@@ -11437,37 +11308,14 @@ snapshots:
 
   p-try@2.2.0: {}
 
-  pac-proxy-agent@7.0.1:
-    dependencies:
-      '@tootallnate/quickjs-emscripten': 0.23.0
-      agent-base: 7.1.1
-      debug: 4.3.4
-      get-uri: 6.0.3
-      http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.4
-      pac-resolver: 7.0.1
-      socks-proxy-agent: 8.0.3
-    transitivePeerDependencies:
-      - supports-color
-
-  pac-resolver@7.0.1:
-    dependencies:
-      degenerator: 5.0.1
-      netmask: 2.0.2
+  package-json-from-dist@1.0.1: {}
 
   package-json@6.5.0:
     dependencies:
       got: 12.6.1
       registry-auth-token: 4.2.2
       registry-url: 5.1.0
-      semver: 7.6.2
-
-  package-json@8.1.1:
-    dependencies:
-      got: 12.6.1
-      registry-auth-token: 5.0.2
-      registry-url: 6.0.1
-      semver: 7.6.2
+      semver: 7.7.2
 
   pako@1.0.11: {}
 
@@ -11475,6 +11323,10 @@ snapshots:
     dependencies:
       callsites: 3.1.0
 
+  parent-module@2.0.0:
+    dependencies:
+      callsites: 3.1.0
+
   parent-require@1.0.0: {}
 
   parents@1.0.1:
@@ -11486,30 +11338,26 @@ snapshots:
       asn1.js: 4.10.1
       browserify-aes: 1.2.0
       evp_bytestokey: 1.0.3
-      hash-base: 3.0.4
-      pbkdf2: 3.1.2
+      hash-base: 3.0.5
+      pbkdf2: 3.1.3
       safe-buffer: 5.2.1
 
-  parse-github-url@1.0.2: {}
+  parse-imports-exports@0.2.4:
+    dependencies:
+      parse-statements: 1.0.11
 
   parse-json@5.2.0:
     dependencies:
-      '@babel/code-frame': 7.24.2
+      '@babel/code-frame': 7.27.1
       error-ex: 1.3.2
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
 
-  parse-path@7.0.0:
-    dependencies:
-      protocols: 2.0.1
-
-  parse-url@8.1.0:
-    dependencies:
-      parse-path: 7.0.0
+  parse-statements@1.0.11: {}
 
-  parse5@7.1.2:
+  parse5@7.3.0:
     dependencies:
-      entities: 4.5.0
+      entities: 6.0.1
 
   parseurl@1.3.3: {}
 
@@ -11525,112 +11373,102 @@ snapshots:
 
   path-key@3.1.1: {}
 
-  path-key@4.0.0: {}
-
   path-parse@1.0.7: {}
 
   path-platform@0.11.15: {}
 
   path-scurry@1.11.1:
     dependencies:
-      lru-cache: 10.2.2
-      minipass: 7.1.1
+      lru-cache: 10.4.3
+      minipass: 7.1.2
 
-  path-type@4.0.0: {}
+  path-scurry@2.0.0:
+    dependencies:
+      lru-cache: 11.1.0
+      minipass: 7.1.2
 
-  path-type@5.0.0: {}
+  path-type@4.0.0: {}
 
-  pathe@1.1.2: {}
+  pathe@2.0.3: {}
 
-  pathval@1.1.1: {}
+  pathval@2.0.1: {}
 
-  pbkdf2@3.1.2:
+  pbkdf2@3.1.3:
     dependencies:
-      create-hash: 1.2.0
+      create-hash: 1.1.3
       create-hmac: 1.1.7
-      ripemd160: 2.0.2
+      ripemd160: 2.0.1
       safe-buffer: 5.2.1
-      sha.js: 2.4.11
+      sha.js: 2.4.12
+      to-buffer: 1.2.1
+
+  peowly@1.3.2: {}
 
   performance-now@2.1.0: {}
 
   pg-connection-string@2.6.2: {}
 
-  picocolors@1.0.1: {}
+  picocolors@1.1.1: {}
 
   picomatch@2.3.1: {}
 
+  picomatch@4.0.2: {}
+
   pidtree@0.6.0: {}
 
   pify@2.3.0: {}
 
-  pkg-types@1.1.1:
-    dependencies:
-      confbox: 0.1.7
-      mlly: 1.7.0
-      pathe: 1.1.2
-
   pkg-up@3.1.0:
     dependencies:
       find-up: 3.0.0
 
-  poolifier@4.0.10: {}
+  poolifier@5.0.2: {}
 
-  possible-typed-array-names@1.0.0: {}
+  possible-typed-array-names@1.1.0: {}
 
-  postcss-import@13.0.0(postcss@8.4.38):
+  postcss-import@13.0.0(postcss@8.5.6):
     dependencies:
-      postcss: 8.4.38
+      postcss: 8.5.6
       postcss-value-parser: 4.2.0
       read-cache: 1.0.0
-      resolve: 1.22.8
+      resolve: 1.22.10
 
-  postcss-selector-parser@6.0.16:
+  postcss-selector-parser@6.1.2:
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
   postcss-value-parser@4.2.0: {}
 
-  postcss@8.4.38:
+  postcss@8.5.6:
     dependencies:
-      nanoid: 3.3.7
-      picocolors: 1.0.1
-      source-map-js: 1.2.0
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
 
-  prebuild-install@7.1.2:
+  prebuild-install@7.1.3:
     dependencies:
-      detect-libc: 2.0.3
+      detect-libc: 2.0.4
       expand-template: 2.0.3
       github-from-package: 0.0.0
       minimist: 1.2.8
       mkdirp-classic: 0.5.3
-      napi-build-utils: 1.0.2
-      node-abi: 3.62.0
-      pump: 3.0.0
+      napi-build-utils: 2.0.0
+      node-abi: 3.75.0
+      pump: 3.0.3
       rc: 1.2.8
       simple-get: 4.0.1
-      tar-fs: 2.1.1
+      tar-fs: 2.1.3
       tunnel-agent: 0.6.0
 
   prelude-ls@1.1.2: {}
 
   prelude-ls@1.2.1: {}
 
-  prettier-linter-helpers@1.0.0:
-    dependencies:
-      fast-diff: 1.3.0
-
-  prettier@3.2.5: {}
+  prettier@3.6.2: {}
 
   pretty-bytes@5.6.0: {}
 
-  pretty-format@29.7.0:
-    dependencies:
-      '@jest/schemas': 29.6.3
-      ansi-styles: 5.2.0
-      react-is: 18.3.1
-
   pretty-hrtime@1.0.3: {}
 
   process-nextick-args@2.0.1: {}
@@ -11648,20 +11486,17 @@ snapshots:
       retry: 0.12.0
     optional: true
 
-  promise.allsettled@1.0.7:
+  prop-types@15.8.1:
     dependencies:
-      array.prototype.map: 1.0.7
-      call-bind: 1.0.7
-      define-properties: 1.2.1
-      es-abstract: 1.23.3
-      get-intrinsic: 1.2.4
-      iterate-value: 1.0.2
+      loose-envify: 1.4.0
+      object-assign: 4.1.1
+      react-is: 16.13.1
 
   proto-list@1.2.4: {}
 
   protocol-buffers-encodings@1.2.0:
     dependencies:
-      b4a: 1.6.6
+      b4a: 1.6.7
       signed-varint: 2.0.1
       varint: 5.0.0
 
@@ -11676,44 +11511,29 @@ snapshots:
       signed-varint: 2.0.1
       varint: 5.0.2
 
-  protocols@2.0.1: {}
-
-  proxy-agent@6.4.0:
+  psl@1.15.0:
     dependencies:
-      agent-base: 7.1.1
-      debug: 4.3.4
-      http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.4
-      lru-cache: 7.18.3
-      pac-proxy-agent: 7.0.1
-      proxy-from-env: 1.1.0
-      socks-proxy-agent: 8.0.3
-    transitivePeerDependencies:
-      - supports-color
-
-  proxy-from-env@1.1.0: {}
-
-  psl@1.9.0: {}
+      punycode: 2.3.1
 
   public-encrypt@4.0.3:
     dependencies:
-      bn.js: 4.12.0
-      browserify-rsa: 4.1.0
+      bn.js: 4.12.2
+      browserify-rsa: 4.1.1
       create-hash: 1.2.0
       parse-asn1: 5.1.7
       randombytes: 2.1.0
       safe-buffer: 5.2.1
 
-  pump@3.0.0:
+  pump@3.0.3:
     dependencies:
-      end-of-stream: 1.4.4
+      end-of-stream: 1.4.5
       once: 1.4.0
 
   pumpify@2.0.1:
     dependencies:
       duplexify: 4.1.3
       inherits: 2.0.4
-      pump: 3.0.0
+      pump: 3.0.3
 
   punycode@1.4.1: {}
 
@@ -11723,13 +11543,9 @@ snapshots:
     dependencies:
       escape-goat: 2.1.1
 
-  pupa@3.1.0:
-    dependencies:
-      escape-goat: 4.0.0
-
-  qs@6.12.1:
+  qs@6.14.0:
     dependencies:
-      side-channel: 1.0.6
+      side-channel: 1.1.0
 
   qs@6.5.3: {}
 
@@ -11749,8 +11565,6 @@ snapshots:
       minimist: 1.2.8
       through2: 2.0.5
 
-  rambda@9.2.0: {}
-
   randombytes@2.1.0:
     dependencies:
       safe-buffer: 5.2.1
@@ -11769,7 +11583,7 @@ snapshots:
       minimist: 1.2.8
       strip-json-comments: 2.0.1
 
-  react-is@18.3.1: {}
+  react-is@16.13.1: {}
 
   read-cache@1.0.0:
     dependencies:
@@ -11799,78 +11613,48 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
-  rechoir@0.6.2:
-    dependencies:
-      resolve: 1.22.8
-
   rechoir@0.8.0:
     dependencies:
-      resolve: 1.22.8
+      resolve: 1.22.10
 
   reflect-metadata@0.2.2: {}
 
-  regexp.prototype.flags@1.5.2:
+  reflect.getprototypeof@1.0.10:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.0
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      which-builtin-type: 1.2.1
+
+  regexp.prototype.flags@1.5.4:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
       es-errors: 1.3.0
+      get-proto: 1.0.1
+      gopd: 1.2.0
       set-function-name: 2.0.2
 
   registry-auth-token@4.2.2:
     dependencies:
       rc: 1.2.8
 
-  registry-auth-token@5.0.2:
-    dependencies:
-      '@pnpm/npm-conf': 2.2.2
-
   registry-url@5.1.0:
     dependencies:
       rc: 1.2.8
 
-  registry-url@6.0.1:
-    dependencies:
-      rc: 1.2.8
-
   reinterval@1.1.0: {}
 
-  release-it@17.3.0(typescript@5.4.5):
-    dependencies:
-      '@iarna/toml': 2.2.5
-      '@octokit/rest': 20.1.1
-      async-retry: 1.3.3
-      chalk: 5.3.0
-      cosmiconfig: 9.0.0(typescript@5.4.5)
-      execa: 8.0.1
-      git-url-parse: 14.0.0
-      globby: 14.0.1
-      got: 12.6.1
-      inquirer: 9.2.22
-      is-ci: 3.0.1
-      issue-parser: 7.0.0
-      lodash: 4.17.21
-      mime-types: 2.1.35
-      new-github-release-url: 2.0.0
-      node-fetch: 3.3.2
-      open: 10.1.0
-      ora: 8.0.1
-      os-name: 5.1.0
-      promise.allsettled: 1.0.7
-      proxy-agent: 6.4.0
-      semver: 7.6.2
-      shelljs: 0.8.5
-      update-notifier: 7.0.0
-      url-join: 5.0.0
-      wildcard-match: 5.1.3
-      yargs-parser: 21.1.1
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
+  repeat-string@1.6.1: {}
 
   request@2.88.2:
     dependencies:
       aws-sign2: 0.7.0
-      aws4: 1.13.0
+      aws4: 1.13.2
       caseless: 0.12.0
       combined-stream: 1.0.8
       extend: 3.0.2
@@ -11906,14 +11690,15 @@ snapshots:
 
   resolve-pkg-maps@1.0.0: {}
 
-  resolve@1.19.0:
+  resolve@1.22.10:
     dependencies:
-      is-core-module: 2.13.1
+      is-core-module: 2.16.1
       path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
 
-  resolve@1.22.8:
+  resolve@2.0.0-next.5:
     dependencies:
-      is-core-module: 2.13.1
+      is-core-module: 2.16.1
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
 
@@ -11931,65 +11716,69 @@ snapshots:
       onetime: 5.1.2
       signal-exit: 3.0.7
 
-  restore-cursor@4.0.0:
+  restore-cursor@5.1.0:
     dependencies:
-      onetime: 5.1.2
-      signal-exit: 3.0.7
+      onetime: 7.0.0
+      signal-exit: 4.1.0
 
   retimer@3.0.0: {}
 
   retry@0.12.0:
     optional: true
 
-  retry@0.13.1: {}
+  reusify@1.1.0: {}
 
-  reusify@1.0.4: {}
-
-  rfdc@1.3.1: {}
+  rfdc@1.4.1: {}
 
   rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
 
-  rimraf@5.0.7:
+  rimraf@6.0.1:
+    dependencies:
+      glob: 11.0.3
+      package-json-from-dist: 1.0.1
+
+  ripemd160@2.0.1:
     dependencies:
-      glob: 10.3.15
+      hash-base: 2.0.2
+      inherits: 2.0.4
 
   ripemd160@2.0.2:
     dependencies:
-      hash-base: 3.1.0
+      hash-base: 3.0.5
       inherits: 2.0.4
 
-  rollup@4.17.2:
+  rollup@4.44.1:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.8
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.17.2
-      '@rollup/rollup-android-arm64': 4.17.2
-      '@rollup/rollup-darwin-arm64': 4.17.2
-      '@rollup/rollup-darwin-x64': 4.17.2
-      '@rollup/rollup-linux-arm-gnueabihf': 4.17.2
-      '@rollup/rollup-linux-arm-musleabihf': 4.17.2
-      '@rollup/rollup-linux-arm64-gnu': 4.17.2
-      '@rollup/rollup-linux-arm64-musl': 4.17.2
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2
-      '@rollup/rollup-linux-riscv64-gnu': 4.17.2
-      '@rollup/rollup-linux-s390x-gnu': 4.17.2
-      '@rollup/rollup-linux-x64-gnu': 4.17.2
-      '@rollup/rollup-linux-x64-musl': 4.17.2
-      '@rollup/rollup-win32-arm64-msvc': 4.17.2
-      '@rollup/rollup-win32-ia32-msvc': 4.17.2
-      '@rollup/rollup-win32-x64-msvc': 4.17.2
+      '@rollup/rollup-android-arm-eabi': 4.44.1
+      '@rollup/rollup-android-arm64': 4.44.1
+      '@rollup/rollup-darwin-arm64': 4.44.1
+      '@rollup/rollup-darwin-x64': 4.44.1
+      '@rollup/rollup-freebsd-arm64': 4.44.1
+      '@rollup/rollup-freebsd-x64': 4.44.1
+      '@rollup/rollup-linux-arm-gnueabihf': 4.44.1
+      '@rollup/rollup-linux-arm-musleabihf': 4.44.1
+      '@rollup/rollup-linux-arm64-gnu': 4.44.1
+      '@rollup/rollup-linux-arm64-musl': 4.44.1
+      '@rollup/rollup-linux-loongarch64-gnu': 4.44.1
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.44.1
+      '@rollup/rollup-linux-riscv64-gnu': 4.44.1
+      '@rollup/rollup-linux-riscv64-musl': 4.44.1
+      '@rollup/rollup-linux-s390x-gnu': 4.44.1
+      '@rollup/rollup-linux-x64-gnu': 4.44.1
+      '@rollup/rollup-linux-x64-musl': 4.44.1
+      '@rollup/rollup-win32-arm64-msvc': 4.44.1
+      '@rollup/rollup-win32-ia32-msvc': 4.44.1
+      '@rollup/rollup-win32-x64-msvc': 4.44.1
       fsevents: 2.3.3
 
-  rrweb-cssom@0.6.0: {}
-
-  run-applescript@7.0.0: {}
+  rrweb-cssom@0.8.0: {}
 
   run-async@2.4.1: {}
 
-  run-async@3.0.0: {}
-
   run-parallel@1.2.0:
     dependencies:
       queue-microtask: 1.2.3
@@ -11998,28 +11787,30 @@ snapshots:
     dependencies:
       tslib: 1.14.1
 
-  rxjs@7.8.1:
-    dependencies:
-      tslib: 2.6.2
-
-  safe-array-concat@1.1.2:
+  safe-array-concat@1.1.3:
     dependencies:
-      call-bind: 1.0.7
-      get-intrinsic: 1.2.4
-      has-symbols: 1.0.3
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+      has-symbols: 1.1.0
       isarray: 2.0.5
 
   safe-buffer@5.1.2: {}
 
   safe-buffer@5.2.1: {}
 
-  safe-regex-test@1.0.3:
+  safe-push-apply@1.0.0:
     dependencies:
-      call-bind: 1.0.7
       es-errors: 1.3.0
-      is-regex: 1.1.4
+      isarray: 2.0.5
+
+  safe-regex-test@1.1.0:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-regex: 1.2.1
 
-  safe-stable-stringify@2.4.3: {}
+  safe-stable-stringify@2.5.0: {}
 
   safer-buffer@2.1.2: {}
 
@@ -12041,38 +11832,32 @@ snapshots:
 
   semver-diff@3.1.1:
     dependencies:
-      semver: 7.6.2
+      semver: 7.7.2
 
-  semver-diff@4.0.0:
-    dependencies:
-      semver: 7.6.2
-
-  semver@7.6.2: {}
+  semver@7.7.2: {}
 
-  send@0.18.0:
+  send@1.2.0:
     dependencies:
-      debug: 2.6.9
-      depd: 2.0.0
-      destroy: 1.2.0
-      encodeurl: 1.0.2
+      debug: 4.4.1
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       etag: 1.8.1
-      fresh: 0.5.2
+      fresh: 2.0.0
       http-errors: 2.0.0
-      mime: 1.6.0
+      mime-types: 3.0.1
       ms: 2.1.3
       on-finished: 2.4.1
       range-parser: 1.2.1
-      statuses: 2.0.1
+      statuses: 2.0.2
     transitivePeerDependencies:
       - supports-color
 
-  serve-static@1.15.0:
+  serve-static@2.2.0:
     dependencies:
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       parseurl: 1.3.3
-      send: 0.18.0
+      send: 1.2.0
     transitivePeerDependencies:
       - supports-color
 
@@ -12083,8 +11868,8 @@ snapshots:
       define-data-property: 1.1.4
       es-errors: 1.3.0
       function-bind: 1.1.2
-      get-intrinsic: 1.2.4
-      gopd: 1.0.1
+      get-intrinsic: 1.3.0
+      gopd: 1.2.0
       has-property-descriptors: 1.0.2
 
   set-function-name@2.0.2:
@@ -12094,12 +11879,19 @@ snapshots:
       functions-have-names: 1.2.3
       has-property-descriptors: 1.0.2
 
+  set-proto@1.0.0:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+
   setprototypeof@1.2.0: {}
 
-  sha.js@2.4.11:
+  sha.js@2.4.12:
     dependencies:
       inherits: 2.0.4
       safe-buffer: 5.2.1
+      to-buffer: 1.2.1
 
   shallow-copy@0.0.1: {}
 
@@ -12113,24 +11905,39 @@ snapshots:
 
   shebang-regex@3.0.0: {}
 
-  shell-quote@1.8.1: {}
-
-  shelljs@0.8.5:
-    dependencies:
-      glob: 7.2.3
-      interpret: 1.4.0
-      rechoir: 0.6.2
+  shell-quote@1.8.3: {}
 
   showdown@1.9.1:
     dependencies:
       yargs: 14.2.3
 
-  side-channel@1.0.6:
+  side-channel-list@1.0.0:
+    dependencies:
+      es-errors: 1.3.0
+      object-inspect: 1.13.4
+
+  side-channel-map@1.0.1:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      object-inspect: 1.13.4
+
+  side-channel-weakmap@1.0.2:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      get-intrinsic: 1.2.4
-      object-inspect: 1.13.1
+      get-intrinsic: 1.3.0
+      object-inspect: 1.13.4
+      side-channel-map: 1.0.1
+
+  side-channel@1.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      object-inspect: 1.13.4
+      side-channel-list: 1.0.0
+      side-channel-map: 1.0.1
+      side-channel-weakmap: 1.0.2
 
   siginfo@2.0.0: {}
 
@@ -12162,8 +11969,6 @@ snapshots:
 
   slash@3.0.0: {}
 
-  slash@5.1.0: {}
-
   slice-ansi@5.0.0:
     dependencies:
       ansi-styles: 6.2.1
@@ -12174,36 +11979,30 @@ snapshots:
       ansi-styles: 6.2.1
       is-fullwidth-code-point: 5.0.0
 
-  smart-buffer@4.2.0: {}
+  smart-buffer@4.2.0:
+    optional: true
 
   socks-proxy-agent@6.2.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4
-      socks: 2.8.3
+      debug: 4.4.1
+      socks: 2.8.5
     transitivePeerDependencies:
       - supports-color
     optional: true
 
-  socks-proxy-agent@8.0.3:
-    dependencies:
-      agent-base: 7.1.1
-      debug: 4.3.4
-      socks: 2.8.3
-    transitivePeerDependencies:
-      - supports-color
-
-  socks@2.8.3:
+  socks@2.8.5:
     dependencies:
       ip-address: 9.0.5
       smart-buffer: 4.2.0
+    optional: true
 
   sonic-boom@1.4.1:
     dependencies:
       atomic-sleep: 1.0.0
       flatstr: 1.0.12
 
-  source-map-js@1.2.0: {}
+  source-map-js@1.2.1: {}
 
   source-map-support@0.5.21:
     dependencies:
@@ -12225,19 +12024,20 @@ snapshots:
   spdx-expression-parse@4.0.0:
     dependencies:
       spdx-exceptions: 2.5.0
-      spdx-license-ids: 3.0.17
+      spdx-license-ids: 3.0.21
 
-  spdx-license-ids@3.0.17: {}
+  spdx-license-ids@3.0.21: {}
 
   split2@4.2.0: {}
 
-  sprintf-js@1.1.3: {}
+  sprintf-js@1.1.3:
+    optional: true
 
   sqlite3@5.1.7:
     dependencies:
       bindings: 1.5.0
-      node-addon-api: 7.1.0
-      prebuild-install: 7.1.2
+      node-addon-api: 7.1.1
+      prebuild-install: 7.1.3
       tar: 6.2.1
     optionalDependencies:
       node-gyp: 8.4.1
@@ -12266,11 +12066,11 @@ snapshots:
       minipass: 3.3.6
     optional: true
 
-  stack-trace@0.0.10: {}
+  stable-hash-x@0.2.0: {}
 
-  stack-utils@2.0.6:
-    dependencies:
-      escape-string-regexp: 2.0.0
+  stable-hash@0.0.5: {}
+
+  stack-trace@0.0.10: {}
 
   stackback@0.0.2: {}
 
@@ -12288,7 +12088,7 @@ snapshots:
       has: 1.0.4
       magic-string: 0.25.1
       merge-source-map: 1.0.4
-      object-inspect: 1.13.1
+      object-inspect: 1.13.4
       readable-stream: 2.3.8
       scope-analyzer: 2.1.2
       shallow-copy: 0.0.1
@@ -12297,13 +12097,14 @@ snapshots:
 
   statuses@2.0.1: {}
 
-  std-env@3.7.0: {}
+  statuses@2.0.2: {}
 
-  stdin-discarder@0.2.2: {}
+  std-env@3.9.0: {}
 
-  stop-iteration-iterator@1.0.0:
+  stop-iteration-iterator@1.1.0:
     dependencies:
-      internal-slot: 1.0.7
+      es-errors: 1.3.0
+      internal-slot: 1.1.0
 
   stream-browserify@3.0.0:
     dependencies:
@@ -12335,7 +12136,7 @@ snapshots:
 
   stream-template@0.0.10:
     dependencies:
-      end-of-stream: 1.4.4
+      end-of-stream: 1.4.5
       readable-stream: 2.3.8
 
   streaming-json-stringify@3.1.0:
@@ -12374,30 +12175,55 @@ snapshots:
       emoji-regex: 9.2.2
       strip-ansi: 7.1.0
 
-  string-width@7.1.0:
+  string-width@7.2.0:
     dependencies:
-      emoji-regex: 10.3.0
-      get-east-asian-width: 1.2.0
+      emoji-regex: 10.4.0
+      get-east-asian-width: 1.3.0
       strip-ansi: 7.1.0
 
-  string.prototype.trim@1.2.9:
+  string.prototype.matchall@4.0.12:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.0
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      gopd: 1.2.0
+      has-symbols: 1.1.0
+      internal-slot: 1.1.0
+      regexp.prototype.flags: 1.5.4
+      set-function-name: 2.0.2
+      side-channel: 1.1.0
+
+  string.prototype.repeat@1.0.0:
+    dependencies:
+      define-properties: 1.2.1
+      es-abstract: 1.24.0
+
+  string.prototype.trim@1.2.10:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-data-property: 1.1.4
       define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-object-atoms: 1.0.0
+      es-abstract: 1.24.0
+      es-object-atoms: 1.1.1
+      has-property-descriptors: 1.0.2
 
-  string.prototype.trimend@1.0.8:
+  string.prototype.trimend@1.0.9:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
       define-properties: 1.2.1
-      es-object-atoms: 1.0.0
+      es-object-atoms: 1.1.1
 
   string.prototype.trimstart@1.0.8:
     dependencies:
-      call-bind: 1.0.7
+      call-bind: 1.0.8
       define-properties: 1.2.1
-      es-object-atoms: 1.0.0
+      es-object-atoms: 1.1.1
 
   string_decoder@1.1.1:
     dependencies:
@@ -12425,21 +12251,19 @@ snapshots:
 
   strip-ansi@7.1.0:
     dependencies:
-      ansi-regex: 6.0.1
+      ansi-regex: 6.1.0
 
   strip-bom@3.0.0: {}
 
   strip-final-newline@2.0.0: {}
 
-  strip-final-newline@3.0.0: {}
-
   strip-json-comments@2.0.1: {}
 
   strip-json-comments@3.1.1: {}
 
-  strip-literal@2.1.0:
+  strip-literal@3.0.0:
     dependencies:
-      js-tokens: 9.0.0
+      js-tokens: 9.0.1
 
   subarg@1.0.0:
     dependencies:
@@ -12459,14 +12283,11 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  svg-tags@1.0.0: {}
-
   symbol-tree@3.2.4: {}
 
-  synckit@0.8.8:
+  synckit@0.11.8:
     dependencies:
-      '@pkgr/core': 0.1.1
-      tslib: 2.6.2
+      '@pkgr/core': 0.2.7
 
   syntax-error@1.4.0:
     dependencies:
@@ -12474,19 +12295,19 @@ snapshots:
 
   tachyons@4.12.0: {}
 
-  tapable@2.2.1: {}
+  tapable@2.2.2: {}
 
-  tar-fs@2.1.1:
+  tar-fs@2.1.3:
     dependencies:
       chownr: 1.1.4
       mkdirp-classic: 0.5.3
-      pump: 3.0.0
+      pump: 3.0.3
       tar-stream: 2.2.0
 
   tar-stream@2.2.0:
     dependencies:
       bl: 4.1.0
-      end-of-stream: 1.4.4
+      end-of-stream: 1.4.5
       fs-constants: 1.0.0
       inherits: 2.0.4
       readable-stream: 3.6.2
@@ -12500,12 +12321,12 @@ snapshots:
       mkdirp: 1.0.4
       yallist: 4.0.0
 
-  tar@7.1.0:
+  tar@7.4.3:
     dependencies:
       '@isaacs/fs-minipass': 4.0.1
       chownr: 3.0.0
-      minipass: 7.1.1
-      minizlib: 3.0.1
+      minipass: 7.1.2
+      minizlib: 3.0.2
       mkdirp: 3.0.1
       yallist: 5.0.0
 
@@ -12513,23 +12334,21 @@ snapshots:
 
   terser@4.8.1:
     dependencies:
-      acorn: 8.11.3
+      acorn: 8.15.0
       commander: 2.20.3
       source-map: 0.6.1
       source-map-support: 0.5.21
 
-  test-exclude@6.0.0:
+  test-exclude@7.0.1:
     dependencies:
       '@istanbuljs/schema': 0.1.3
-      glob: 7.2.3
-      minimatch: 3.1.2
+      glob: 10.4.5
+      minimatch: 9.0.5
 
   text-extensions@2.4.0: {}
 
   text-hex@1.0.0: {}
 
-  text-table@0.2.0: {}
-
   through2@2.0.5:
     dependencies:
       readable-stream: 2.3.8
@@ -12554,17 +12373,32 @@ snapshots:
 
   timestring@6.0.0: {}
 
-  tinybench@2.8.0: {}
+  tinybench@2.9.0: {}
+
+  tinyexec@0.3.2: {}
 
-  tinypool@0.8.4: {}
+  tinyexec@1.0.1: {}
 
-  tinyspy@2.2.1: {}
+  tinyglobby@0.2.14:
+    dependencies:
+      fdir: 6.4.6(picomatch@4.0.2)
+      picomatch: 4.0.2
+
+  tinypool@1.1.1: {}
+
+  tinyrainbow@2.0.0: {}
+
+  tinyspy@4.0.3: {}
 
   tmp@0.0.33:
     dependencies:
       os-tmpdir: 1.0.2
 
-  to-fast-properties@2.0.0: {}
+  to-buffer@1.2.1:
+    dependencies:
+      isarray: 2.0.5
+      safe-buffer: 5.2.1
+      typed-array-buffer: 1.0.3
 
   to-regex-range@5.0.1:
     dependencies:
@@ -12576,18 +12410,14 @@ snapshots:
 
   tough-cookie@4.1.4:
     dependencies:
-      psl: 1.9.0
+      psl: 1.15.0
       punycode: 2.3.1
       universalify: 0.2.0
       url-parse: 1.5.10
 
   tr46@0.0.3: {}
 
-  tr46@4.1.1:
-    dependencies:
-      punycode: 2.3.1
-
-  tr46@5.0.0:
+  tr46@5.1.1:
     dependencies:
       punycode: 2.3.1
 
@@ -12603,40 +12433,38 @@ snapshots:
 
   triple-beam@1.4.1: {}
 
-  ts-api-utils@1.3.0(typescript@5.4.5):
+  ts-api-utils@2.1.0(typescript@5.8.3):
+    dependencies:
+      typescript: 5.8.3
+
+  ts-declaration-location@1.0.7(typescript@5.8.3):
     dependencies:
-      typescript: 5.4.5
+      picomatch: 4.0.2
+      typescript: 5.8.3
 
-  ts-morph@22.0.0:
+  ts-morph@26.0.0:
     dependencies:
-      '@ts-morph/common': 0.23.0
-      code-block-writer: 13.0.1
+      '@ts-morph/common': 0.27.0
+      code-block-writer: 13.0.3
 
-  ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5):
+  ts-node@10.9.2(@types/node@24.0.10)(typescript@5.8.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 20.12.12
-      acorn: 8.11.3
-      acorn-walk: 8.3.2
+      '@types/node': 24.0.10
+      acorn: 8.15.0
+      acorn-walk: 8.3.4
       arg: 4.1.3
       create-require: 1.1.1
       diff: 4.0.2
       make-error: 1.3.6
-      typescript: 5.4.5
+      typescript: 5.8.3
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
-  tsconfig-paths@3.15.0:
-    dependencies:
-      '@types/json5': 0.0.29
-      json5: 1.0.2
-      minimist: 1.2.8
-      strip-bom: 3.0.0
-
   tsconfig-paths@4.2.0:
     dependencies:
       json5: 2.2.3
@@ -12645,12 +12473,13 @@ snapshots:
 
   tslib@1.14.1: {}
 
-  tslib@2.6.2: {}
+  tslib@2.8.1:
+    optional: true
 
-  tsx@4.10.5:
+  tsx@4.20.3:
     dependencies:
-      esbuild: 0.20.2
-      get-tsconfig: 4.7.5
+      esbuild: 0.25.5
+      get-tsconfig: 4.10.1
     optionalDependencies:
       fsevents: 2.3.3
 
@@ -12681,49 +12510,42 @@ snapshots:
 
   type-component@0.0.1: {}
 
-  type-detect@4.0.8: {}
-
   type-fest@0.20.2: {}
 
-  type-fest@0.21.3: {}
-
-  type-fest@1.4.0: {}
-
-  type-fest@2.19.0: {}
+  type@2.7.3: {}
 
-  type@2.7.2: {}
-
-  typed-array-buffer@1.0.2:
+  typed-array-buffer@1.0.3:
     dependencies:
-      call-bind: 1.0.7
+      call-bound: 1.0.4
       es-errors: 1.3.0
-      is-typed-array: 1.1.13
+      is-typed-array: 1.1.15
 
-  typed-array-byte-length@1.0.1:
+  typed-array-byte-length@1.0.3:
     dependencies:
-      call-bind: 1.0.7
-      for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
-      is-typed-array: 1.1.13
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      has-proto: 1.2.0
+      is-typed-array: 1.1.15
 
-  typed-array-byte-offset@1.0.2:
+  typed-array-byte-offset@1.0.4:
     dependencies:
       available-typed-arrays: 1.0.7
-      call-bind: 1.0.7
-      for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
-      is-typed-array: 1.1.13
-
-  typed-array-length@1.0.6:
-    dependencies:
-      call-bind: 1.0.7
-      for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
-      is-typed-array: 1.1.13
-      possible-typed-array-names: 1.0.0
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      has-proto: 1.2.0
+      is-typed-array: 1.1.15
+      reflect.getprototypeof: 1.0.10
+
+  typed-array-length@1.0.7:
+    dependencies:
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      is-typed-array: 1.1.15
+      possible-typed-array-names: 1.1.0
+      reflect.getprototypeof: 1.0.10
 
   typedarray-pool@1.2.0:
     dependencies:
@@ -12736,21 +12558,26 @@ snapshots:
 
   typedarray@0.0.6: {}
 
-  typescript@5.4.5: {}
-
-  ufo@1.5.3: {}
+  typescript-eslint@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3):
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      '@typescript-eslint/parser': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)
+      eslint: 9.30.1(jiti@2.4.2)
+      typescript: 5.8.3
+    transitivePeerDependencies:
+      - supports-color
 
-  uglify-js@3.17.4:
-    optional: true
+  typescript@5.8.3: {}
 
   umd@3.0.3: {}
 
-  unbox-primitive@1.0.2:
+  unbox-primitive@1.1.0:
     dependencies:
-      call-bind: 1.0.7
-      has-bigints: 1.0.2
-      has-symbols: 1.0.3
-      which-boxed-primitive: 1.0.2
+      call-bound: 1.0.4
+      has-bigints: 1.1.0
+      has-symbols: 1.1.0
+      which-boxed-primitive: 1.1.1
 
   undeclared-identifiers@1.1.3:
     dependencies:
@@ -12760,7 +12587,9 @@ snapshots:
       simple-concat: 1.0.1
       xtend: 4.0.2
 
-  undici-types@5.26.5: {}
+  undici-types@6.21.0: {}
+
+  undici-types@7.8.0: {}
 
   unicorn-magic@0.1.0: {}
 
@@ -12780,23 +12609,39 @@ snapshots:
     dependencies:
       crypto-random-string: 2.0.0
 
-  unique-string@3.0.0:
-    dependencies:
-      crypto-random-string: 4.0.0
-
-  universal-user-agent@6.0.1: {}
-
   universalify@0.2.0: {}
 
   universalify@2.0.1: {}
 
-  unpipe@1.0.0: {}
-
-  update-browserslist-db@1.0.16(browserslist@4.23.0):
+  unrs-resolver@1.10.1:
     dependencies:
-      browserslist: 4.23.0
-      escalade: 3.1.2
-      picocolors: 1.0.1
+      napi-postinstall: 0.3.0
+    optionalDependencies:
+      '@unrs/resolver-binding-android-arm-eabi': 1.10.1
+      '@unrs/resolver-binding-android-arm64': 1.10.1
+      '@unrs/resolver-binding-darwin-arm64': 1.10.1
+      '@unrs/resolver-binding-darwin-x64': 1.10.1
+      '@unrs/resolver-binding-freebsd-x64': 1.10.1
+      '@unrs/resolver-binding-linux-arm-gnueabihf': 1.10.1
+      '@unrs/resolver-binding-linux-arm-musleabihf': 1.10.1
+      '@unrs/resolver-binding-linux-arm64-gnu': 1.10.1
+      '@unrs/resolver-binding-linux-arm64-musl': 1.10.1
+      '@unrs/resolver-binding-linux-ppc64-gnu': 1.10.1
+      '@unrs/resolver-binding-linux-riscv64-gnu': 1.10.1
+      '@unrs/resolver-binding-linux-riscv64-musl': 1.10.1
+      '@unrs/resolver-binding-linux-s390x-gnu': 1.10.1
+      '@unrs/resolver-binding-linux-x64-gnu': 1.10.1
+      '@unrs/resolver-binding-linux-x64-musl': 1.10.1
+      '@unrs/resolver-binding-wasm32-wasi': 1.10.1
+      '@unrs/resolver-binding-win32-arm64-msvc': 1.10.1
+      '@unrs/resolver-binding-win32-ia32-msvc': 1.10.1
+      '@unrs/resolver-binding-win32-x64-msvc': 1.10.1
+
+  update-browserslist-db@1.1.3(browserslist@4.25.1):
+    dependencies:
+      browserslist: 4.25.1
+      escalade: 3.2.0
+      picocolors: 1.1.1
 
   update-notifier@5.1.0:
     dependencies:
@@ -12811,46 +12656,29 @@ snapshots:
       is-yarn-global: 0.3.0
       latest-version: 5.1.0
       pupa: 2.1.1
-      semver: 7.6.2
+      semver: 7.7.2
       semver-diff: 3.1.1
       xdg-basedir: 4.0.0
 
-  update-notifier@7.0.0:
-    dependencies:
-      boxen: 7.1.1
-      chalk: 5.3.0
-      configstore: 6.0.0
-      import-lazy: 4.0.0
-      is-in-ci: 0.1.0
-      is-installed-globally: 0.4.0
-      is-npm: 6.0.0
-      latest-version: 7.0.0
-      pupa: 3.1.0
-      semver: 7.6.2
-      semver-diff: 4.0.0
-      xdg-basedir: 5.1.0
-
   upper-case@1.1.3: {}
 
   uri-js@4.4.1:
     dependencies:
       punycode: 2.3.1
 
-  url-join@5.0.0: {}
-
   url-parse@1.5.10:
     dependencies:
       querystringify: 2.2.0
       requires-port: 1.0.0
 
-  url@0.11.3:
+  url@0.11.4:
     dependencies:
       punycode: 1.4.1
-      qs: 6.12.1
+      qs: 6.14.0
 
-  utf-8-validate@6.0.4:
+  utf-8-validate@6.0.5:
     dependencies:
-      node-gyp-build: 4.8.1
+      node-gyp-build: 4.8.4
     optional: true
 
   util-deprecate@1.0.2: {}
@@ -12864,10 +12692,10 @@ snapshots:
   util@0.12.5:
     dependencies:
       inherits: 2.0.4
-      is-arguments: 1.1.1
-      is-generator-function: 1.0.10
-      is-typed-array: 1.1.13
-      which-typed-array: 1.1.15
+      is-arguments: 1.2.0
+      is-generator-function: 1.1.0
+      is-typed-array: 1.1.15
+      which-typed-array: 1.1.19
 
   uuid-parse@1.1.0: {}
 
@@ -12875,9 +12703,9 @@ snapshots:
 
   v8-compile-cache-lib@3.0.1: {}
 
-  v8-to-istanbul@9.2.0:
+  v8-to-istanbul@9.3.0:
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.25
+      '@jridgewell/trace-mapping': 0.3.29
       '@types/istanbul-lib-coverage': 2.0.6
       convert-source-map: 2.0.0
 
@@ -12891,101 +12719,122 @@ snapshots:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  vite-node@1.6.0(@types/node@20.12.12):
+  vite-node@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0):
     dependencies:
       cac: 6.7.14
-      debug: 4.3.4
-      pathe: 1.1.2
-      picocolors: 1.0.1
-      vite: 5.2.11(@types/node@20.12.12)
+      debug: 4.4.1
+      es-module-lexer: 1.7.0
+      pathe: 2.0.3
+      vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
     transitivePeerDependencies:
       - '@types/node'
+      - jiti
       - less
       - lightningcss
       - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
       - terser
+      - tsx
+      - yaml
 
-  vite@5.2.11(@types/node@20.12.12):
+  vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0):
     dependencies:
-      esbuild: 0.20.2
-      postcss: 8.4.38
-      rollup: 4.17.2
+      esbuild: 0.25.5
+      fdir: 6.4.6(picomatch@4.0.2)
+      picomatch: 4.0.2
+      postcss: 8.5.6
+      rollup: 4.44.1
+      tinyglobby: 0.2.14
     optionalDependencies:
-      '@types/node': 20.12.12
+      '@types/node': 24.0.10
       fsevents: 2.3.3
-
-  vitest@1.6.0(@types/node@20.12.12)(jsdom@24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)):
-    dependencies:
-      '@vitest/expect': 1.6.0
-      '@vitest/runner': 1.6.0
-      '@vitest/snapshot': 1.6.0
-      '@vitest/spy': 1.6.0
-      '@vitest/utils': 1.6.0
-      acorn-walk: 8.3.2
-      chai: 4.4.1
-      debug: 4.3.4
-      execa: 8.0.1
-      local-pkg: 0.5.0
-      magic-string: 0.30.10
-      pathe: 1.1.2
-      picocolors: 1.0.1
-      std-env: 3.7.0
-      strip-literal: 2.1.0
-      tinybench: 2.8.0
-      tinypool: 0.8.4
-      vite: 5.2.11(@types/node@20.12.12)
-      vite-node: 1.6.0(@types/node@20.12.12)
-      why-is-node-running: 2.2.2
+      jiti: 2.4.2
+      tsx: 4.20.3
+      yaml: 2.8.0
+
+  vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(tsx@4.20.3)(yaml@2.8.0):
+    dependencies:
+      '@types/chai': 5.2.2
+      '@vitest/expect': 3.2.4
+      '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))
+      '@vitest/pretty-format': 3.2.4
+      '@vitest/runner': 3.2.4
+      '@vitest/snapshot': 3.2.4
+      '@vitest/spy': 3.2.4
+      '@vitest/utils': 3.2.4
+      chai: 5.2.0
+      debug: 4.4.1
+      expect-type: 1.2.1
+      magic-string: 0.30.17
+      pathe: 2.0.3
+      picomatch: 4.0.2
+      std-env: 3.9.0
+      tinybench: 2.9.0
+      tinyexec: 0.3.2
+      tinyglobby: 0.2.14
+      tinypool: 1.1.1
+      tinyrainbow: 2.0.0
+      vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
+      vite-node: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)
+      why-is-node-running: 2.3.0
     optionalDependencies:
-      '@types/node': 20.12.12
-      jsdom: 24.0.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      '@types/node': 24.0.10
+      jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)
     transitivePeerDependencies:
+      - jiti
       - less
       - lightningcss
+      - msw
       - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
       - terser
+      - tsx
+      - yaml
 
   vm-browserify@1.1.2: {}
 
-  vue-component-type-helpers@2.0.19: {}
+  vscode-languageserver-textdocument@1.0.12: {}
+
+  vscode-uri@3.1.0: {}
 
-  vue-eslint-parser@9.4.2(eslint@8.57.0):
+  vue-component-type-helpers@2.2.12: {}
+
+  vue-eslint-parser@10.2.0(eslint@9.30.1(jiti@2.4.2)):
     dependencies:
-      debug: 4.3.4
-      eslint: 8.57.0
-      eslint-scope: 7.2.2
-      eslint-visitor-keys: 3.4.3
-      espree: 9.6.1
-      esquery: 1.5.0
-      lodash: 4.17.21
-      semver: 7.6.2
+      debug: 4.4.1
+      eslint: 9.30.1(jiti@2.4.2)
+      eslint-scope: 8.4.0
+      eslint-visitor-keys: 4.2.1
+      espree: 10.4.0
+      esquery: 1.6.0
+      semver: 7.7.2
     transitivePeerDependencies:
       - supports-color
 
-  vue-router@4.3.2(vue@3.4.27(typescript@5.4.5)):
+  vue-router@4.5.1(vue@3.5.17(typescript@5.8.3)):
     dependencies:
-      '@vue/devtools-api': 6.6.1
-      vue: 3.4.27(typescript@5.4.5)
+      '@vue/devtools-api': 6.6.4
+      vue: 3.5.17(typescript@5.8.3)
 
-  vue-toast-notification@3.1.2(vue@3.4.27(typescript@5.4.5)):
+  vue-toast-notification@3.1.3(vue@3.5.17(typescript@5.8.3)):
     dependencies:
-      vue: 3.4.27(typescript@5.4.5)
+      vue: 3.5.17(typescript@5.8.3)
 
-  vue@3.4.27(typescript@5.4.5):
+  vue@3.5.17(typescript@5.8.3):
     dependencies:
-      '@vue/compiler-dom': 3.4.27
-      '@vue/compiler-sfc': 3.4.27
-      '@vue/runtime-dom': 3.4.27
-      '@vue/server-renderer': 3.4.27(vue@3.4.27(typescript@5.4.5))
-      '@vue/shared': 3.4.27
+      '@vue/compiler-dom': 3.5.17
+      '@vue/compiler-sfc': 3.5.17
+      '@vue/runtime-dom': 3.5.17
+      '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.8.3))
+      '@vue/shared': 3.5.17
     optionalDependencies:
-      typescript: 5.4.5
+      typescript: 5.8.3
 
   w3c-xmlserializer@5.0.0:
     dependencies:
@@ -12995,8 +12844,6 @@ snapshots:
     dependencies:
       defaults: 1.0.4
 
-  web-streams-polyfill@3.3.3: {}
-
   webfontloader@1.6.28: {}
 
   webidl-conversions@3.0.1: {}
@@ -13009,14 +12856,9 @@ snapshots:
 
   whatwg-mimetype@4.0.0: {}
 
-  whatwg-url@13.0.0:
-    dependencies:
-      tr46: 4.1.1
-      webidl-conversions: 7.0.0
-
-  whatwg-url@14.0.0:
+  whatwg-url@14.2.0:
     dependencies:
-      tr46: 5.0.0
+      tr46: 5.1.1
       webidl-conversions: 7.0.0
 
   whatwg-url@5.0.0:
@@ -13024,29 +12866,54 @@ snapshots:
       tr46: 0.0.3
       webidl-conversions: 3.0.1
 
-  which-boxed-primitive@1.0.2:
+  which-boxed-primitive@1.1.1:
+    dependencies:
+      is-bigint: 1.1.0
+      is-boolean-object: 1.2.2
+      is-number-object: 1.1.1
+      is-string: 1.1.1
+      is-symbol: 1.1.1
+
+  which-builtin-type@1.2.1:
+    dependencies:
+      call-bound: 1.0.4
+      function.prototype.name: 1.1.8
+      has-tostringtag: 1.0.2
+      is-async-function: 2.1.1
+      is-date-object: 1.1.0
+      is-finalizationregistry: 1.1.1
+      is-generator-function: 1.1.0
+      is-regex: 1.2.1
+      is-weakref: 1.1.1
+      isarray: 2.0.5
+      which-boxed-primitive: 1.1.1
+      which-collection: 1.0.2
+      which-typed-array: 1.1.19
+
+  which-collection@1.0.2:
     dependencies:
-      is-bigint: 1.0.4
-      is-boolean-object: 1.1.2
-      is-number-object: 1.0.7
-      is-string: 1.0.7
-      is-symbol: 1.0.4
+      is-map: 2.0.3
+      is-set: 2.0.3
+      is-weakmap: 2.0.2
+      is-weakset: 2.0.4
 
   which-module@2.0.1: {}
 
-  which-typed-array@1.1.15:
+  which-typed-array@1.1.19:
     dependencies:
       available-typed-arrays: 1.0.7
-      call-bind: 1.0.7
-      for-each: 0.3.3
-      gopd: 1.0.1
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      for-each: 0.3.5
+      get-proto: 1.0.1
+      gopd: 1.2.0
       has-tostringtag: 1.0.2
 
   which@2.0.2:
     dependencies:
       isexe: 2.0.0
 
-  why-is-node-running@2.2.2:
+  why-is-node-running@2.3.0:
     dependencies:
       siginfo: 2.0.0
       stackback: 0.0.2
@@ -13060,64 +12927,46 @@ snapshots:
     dependencies:
       string-width: 4.2.3
 
-  widest-line@4.0.1:
-    dependencies:
-      string-width: 5.1.2
-
-  wildcard-match@5.1.3: {}
-
   windows-release@4.0.0:
     dependencies:
       execa: 4.1.0
 
-  windows-release@5.1.1:
-    dependencies:
-      execa: 5.1.1
-
-  winston-daily-rotate-file@5.0.0(winston@3.13.0):
+  winston-daily-rotate-file@5.0.0(winston@3.17.0):
     dependencies:
       file-stream-rotator: 0.6.1
       object-hash: 3.0.0
       triple-beam: 1.4.1
-      winston: 3.13.0
-      winston-transport: 4.7.0
+      winston: 3.17.0
+      winston-transport: 4.9.0
 
-  winston-transport@4.7.0:
+  winston-transport@4.9.0:
     dependencies:
-      logform: 2.6.0
+      logform: 2.7.0
       readable-stream: 3.6.2
       triple-beam: 1.4.1
 
-  winston@3.13.0:
+  winston@3.17.0:
     dependencies:
       '@colors/colors': 1.6.0
       '@dabh/diagnostics': 2.0.3
-      async: 3.2.5
+      async: 3.2.6
       is-stream: 2.0.1
-      logform: 2.6.0
+      logform: 2.7.0
       one-time: 1.0.0
       readable-stream: 3.6.2
-      safe-stable-stringify: 2.4.3
+      safe-stable-stringify: 2.5.0
       stack-trace: 0.0.10
       triple-beam: 1.4.1
-      winston-transport: 4.7.0
+      winston-transport: 4.9.0
 
   word-wrap@1.2.5: {}
 
-  wordwrap@1.0.0: {}
-
   wrap-ansi@5.1.0:
     dependencies:
       ansi-styles: 3.2.1
       string-width: 3.1.0
       strip-ansi: 5.2.0
 
-  wrap-ansi@6.2.0:
-    dependencies:
-      ansi-styles: 4.3.0
-      string-width: 4.2.3
-      strip-ansi: 6.0.1
-
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -13133,7 +12982,7 @@ snapshots:
   wrap-ansi@9.0.0:
     dependencies:
       ansi-styles: 6.2.1
-      string-width: 7.1.0
+      string-width: 7.2.0
       strip-ansi: 7.1.0
 
   wrappy@1.0.2: {}
@@ -13145,10 +12994,10 @@ snapshots:
       signal-exit: 3.0.7
       typedarray-to-buffer: 3.1.5
 
-  ws@8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.4):
+  ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5):
     optionalDependencies:
-      bufferutil: 4.0.8
-      utf-8-validate: 6.0.4
+      bufferutil: 4.0.9
+      utf-8-validate: 6.0.5
 
   xdg-basedir@4.0.0: {}
 
@@ -13172,7 +13021,7 @@ snapshots:
 
   yallist@5.0.0: {}
 
-  yaml@2.3.4: {}
+  yaml@2.8.0: {}
 
   yargs-parser@15.0.3:
     dependencies:
@@ -13198,7 +13047,7 @@ snapshots:
   yargs@17.7.2:
     dependencies:
       cliui: 8.0.1
-      escalade: 3.1.2
+      escalade: 3.2.0
       get-caller-file: 2.0.5
       require-directory: 2.1.1
       string-width: 4.2.3
@@ -13209,4 +13058,4 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
-  yocto-queue@1.0.0: {}
+  yocto-queue@1.2.1: {}
similarity index 67%
rename from build-requirements.js
rename to scripts/build-requirements.js
index 501933c4fa5ae659809f3ab8f96f13f710d4caa4..ed5459f15b084315c60871a14915ca3dc6fa31b4 100644 (file)
@@ -1,10 +1,11 @@
+import chalk from 'chalk'
 import { readFileSync } from 'node:fs'
 import { exit, version } from 'node:process'
-
-import chalk from 'chalk'
 // eslint-disable-next-line n/no-unpublished-import
 import { satisfies } from 'semver'
 
+import { JSRuntime, runtime } from './runtime.js'
+
 const packageJson = JSON.parse(readFileSync('./package.json', 'utf8'))
 
 /**
@@ -18,9 +19,19 @@ export const checkNodeVersion = () => {
         `Required node version ${enginesNodeVersion} not satisfied with current version ${version}`
       )
     )
-    // eslint-disable-next-line n/no-process-exit
     exit(1)
   }
 }
 
-checkNodeVersion()
+switch (runtime) {
+  case JSRuntime.node:
+    checkNodeVersion()
+    break
+  case JSRuntime.browser:
+  case JSRuntime.bun:
+  case JSRuntime.deno:
+  case JSRuntime.workerd:
+  default:
+    console.warn(chalk.yellow(`Unsupported '${runtime}' runtime detected`))
+    break
+}
similarity index 85%
rename from bundle.js
rename to scripts/bundle.js
index 4443f985160205ae4e0356f8d133f221712a3e6f..21080354efb9ec48f5feef99915277f29728e891 100644 (file)
--- a/bundle.js
@@ -1,20 +1,19 @@
 /* eslint-disable n/no-unpublished-import */
-import { env } from 'node:process'
 
 import chalk from 'chalk'
 import { build } from 'esbuild'
 import { clean } from 'esbuild-plugin-clean'
 import { copy } from 'esbuild-plugin-copy'
+import { env } from 'node:process'
 
 const isDevelopmentBuild = env.BUILD === 'development'
 const sourcemap = !!isDevelopmentBuild
 console.info(chalk.green(`Building in ${isDevelopmentBuild ? 'development' : 'production'} mode`))
 console.time('Build time')
 await build({
-  entryPoints: ['./src/start.ts', './src/charging-station/ChargingStationWorker.ts'],
   bundle: true,
-  platform: 'node',
-  format: 'esm',
+  entryNames: '[name]',
+  entryPoints: ['./src/start.ts', './src/charging-station/ChargingStationWorker.ts'],
   external: [
     '@mikro-orm/*',
     'ajv',
@@ -29,18 +28,16 @@ await build({
     'mongodb',
     'node:*',
     'poolifier',
-    'rambda',
     'tar',
     'winston',
     'winston/*',
     'winston-daily-rotate-file',
-    'ws'
+    'ws',
   ],
-  treeShaking: true,
+  format: 'esm',
   minify: true,
-  sourcemap,
-  entryNames: '[name]',
   outdir: './dist',
+  platform: 'node',
   plugins: [
     clean({
       patterns: [
@@ -50,33 +47,35 @@ await build({
         './dist/assets/json-schemas',
         './dist/assets/station-templates',
         './dist/assets/ui-protocol',
-        './dist/assets/configs-docker'
-      ]
+        './dist/assets/configs-docker',
+      ],
     }),
     copy({
       assets: [
         {
           from: ['./src/assets/config.json'],
-          to: ['./assets']
+          to: ['./assets'],
         },
         {
           from: ['./src/assets/idtags!(-template)*.json'],
-          to: ['./assets']
+          to: ['./assets'],
         },
         {
           from: ['./src/assets/json-schemas/**/*.json'],
-          to: ['./assets/json-schemas']
+          to: ['./assets/json-schemas'],
         },
         {
           from: ['./src/assets/station-templates/**/*.json'],
-          to: ['./assets/station-templates']
+          to: ['./assets/station-templates'],
         },
         {
           from: ['./src/assets/configs-docker/*.json'],
-          to: ['./assets/configs-docker']
-        }
-      ]
-    })
-  ]
+          to: ['./assets/configs-docker'],
+        },
+      ],
+    }),
+  ],
+  sourcemap,
+  treeShaking: true,
 })
 console.timeEnd('Build time')
similarity index 68%
rename from prepare.js
rename to scripts/prepare.js
index bf54524b32ad93a7b167b340fce78e32c201b2b9..12dd76a483d34869f23bbf0af5684bbee21233de 100644 (file)
@@ -4,5 +4,9 @@ const isCIEnvironment = env.CI != null
 const isCFEnvironment = env.VCAP_APPLICATION != null
 if (isCFEnvironment === false && isCIEnvironment === false) {
   // eslint-disable-next-line n/no-unpublished-import
-  import('husky').then(husky => console.warn(husky.default()))
+  import('husky')
+    .then(husky => {
+      return console.warn(husky.default())
+    })
+    .catch(console.error)
 }
diff --git a/scripts/runtime.js b/scripts/runtime.js
new file mode 100644 (file)
index 0000000..202187d
--- /dev/null
@@ -0,0 +1,25 @@
+export const JSRuntime = {
+  browser: 'browser',
+  bun: 'bun',
+  deno: 'deno',
+  node: 'node',
+  workerd: 'workerd',
+}
+
+const isBun = !!globalThis.Bun || !!globalThis.process?.versions?.bun
+const isDeno = !!globalThis.Deno
+const isNode = globalThis.process?.release?.name === 'node'
+// eslint-disable-next-line n/no-unsupported-features/node-builtins
+const isWorkerd = globalThis.navigator?.userAgent === 'Cloudflare-Workers'
+// eslint-disable-next-line n/no-unsupported-features/node-builtins
+const isBrowser = !!globalThis.window && !!globalThis.navigator
+
+export const runtime = (() => {
+  if (isBun) return JSRuntime.bun
+  if (isDeno) return JSRuntime.deno
+  if (isNode) return JSRuntime.node
+  if (isWorkerd) return JSRuntime.workerd
+  if (isBrowser) return JSRuntime.browser
+
+  return 'unknown'
+})()
diff --git a/scripts/skip-preinstall.js b/scripts/skip-preinstall.js
new file mode 100644 (file)
index 0000000..fa9abfe
--- /dev/null
@@ -0,0 +1,8 @@
+import { env, exit } from 'node:process'
+
+const skipPreinstall = Number.parseInt(env.SKIP_PREINSTALL) || env.VCAP_APPLICATION != null
+if (skipPreinstall) {
+  exit()
+} else {
+  exit(1)
+}
diff --git a/sea-config.json b/sea-config.json
new file mode 100644 (file)
index 0000000..1d9de76
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "main": "./dist/start.js",
+  "output": "./dist/evse-simulator.blob",
+  "disableExperimentalSEAWarning": true
+}
diff --git a/skip-preinstall.js b/skip-preinstall.js
deleted file mode 100644 (file)
index e181f0e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-import { env, exit } from 'node:process'
-
-const skipPreinstall = parseInt(env.SKIP_PREINSTALL) || env.VCAP_APPLICATION != null
-if (skipPreinstall) {
-  // eslint-disable-next-line n/no-process-exit
-  exit()
-} else {
-  // eslint-disable-next-line n/no-process-exit
-  exit(1)
-}
index e17480be1567f9cf9aca9a4c17d8ff81f1ba31a1..9959d1cee1d3190b0635c882f666c6f8fe9b1dbe 100644 (file)
@@ -3,13 +3,15 @@ sonar.organization=sap-1
 
 # This is the name and version displayed in the SonarCloud UI.
 sonar.projectName=e-mobility-charging-stations-simulator
-sonar.projectVersion=1.3.3
+# x-release-please-start-version
+sonar.projectVersion=2.0.10
+# x-release-please-end
 
 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
 sonar.sources=src
 sonar.tests=tests
 
-sonar.javascript.lcov.reportPaths=coverage/lcov.info
+sonar.typescript.lcov.reportPaths=coverage/lcov.info
 
 # Encoding of the source code. Default is default system encoding
 #sonar.sourceEncoding=UTF-8
index da32bbcbe72fd610919fc704ab5e1cb21234d5a1..e172ad29bbd5386c843a558f37b4f0080b92807c 100644 (file)
         "username": "admin",
         "password": "admin"
       },
-      "dataPropertyOrder": { "&": ["baseUrl", "protocol", "version", "username", "password"] },
+      "dataPropertyOrder": {
+        "&": ["baseUrl", "protocol", "version", "username", "password"]
+      },
       "color": null,
       "isPrivate": false,
       "metaSortKey": 1661789025528,
index 1312150c6d97b57eae80ffe854adbb4c42fd283f..3d9a5405f723c131bc6cdd607fe6910c9722a867 100644 (file)
         "protocol": "ui",
         "version": "0.0.1"
       },
-      "dataPropertyOrder": { "&": ["baseUrl", "username", "password", "protocol", "version"] },
+      "dataPropertyOrder": {
+        "&": ["baseUrl", "username", "password", "protocol", "version"]
+      },
       "color": null,
       "isPrivate": false,
       "metaSortKey": 1671183662529,
index 4b9b8f6cdbd6ef55cf4bf4fdf65a804640b1b6d1..61be7d7650688a6869420460f9e56e1302a9b204 100644 (file)
@@ -1,19 +1,21 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
+import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns'
 import { randomInt } from 'node:crypto'
 
-import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns'
+import type { ChargingStation } from './ChargingStation.js'
 
 import { BaseError } from '../exception/index.js'
 import { PerformanceStatistics } from '../performance/index.js'
 import {
   AuthorizationStatus,
+  ChargingStationEvents,
   RequestCommand,
   type StartTransactionRequest,
   type StartTransactionResponse,
   type Status,
   StopTransactionReason,
-  type StopTransactionResponse
+  type StopTransactionResponse,
 } from '../types/index.js'
 import {
   clone,
@@ -24,24 +26,24 @@ import {
   logger,
   logPrefix,
   secureRandom,
-  sleep
+  sleep,
 } from '../utils/index.js'
-import type { ChargingStation } from './ChargingStation.js'
-import { checkChargingStation } from './Helpers.js'
+import { checkChargingStationState } from './Helpers.js'
 import { IdTagsCache } from './IdTagsCache.js'
 import { isIdTagAuthorized } from './ocpp/index.js'
 
 export class AutomaticTransactionGenerator {
   private static readonly instances: Map<string, AutomaticTransactionGenerator> = new Map<
-  string,
-  AutomaticTransactionGenerator
+    string,
+    AutomaticTransactionGenerator
   >()
 
   public readonly connectorsStatus: Map<number, Status>
   public started: boolean
+
+  private readonly chargingStation: ChargingStation
   private starting: boolean
   private stopping: boolean
-  private readonly chargingStation: ChargingStation
 
   private constructor (chargingStation: ChargingStation) {
     this.started = false
@@ -52,6 +54,11 @@ export class AutomaticTransactionGenerator {
     this.initializeConnectorsStatus()
   }
 
+  public static deleteInstance (chargingStation: ChargingStation): boolean {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return AutomaticTransactionGenerator.instances.delete(chargingStation.stationInfo!.hashId)
+  }
+
   public static getInstance (
     chargingStation: ChargingStation
   ): AutomaticTransactionGenerator | undefined {
@@ -67,13 +74,8 @@ export class AutomaticTransactionGenerator {
     return AutomaticTransactionGenerator.instances.get(chargingStation.stationInfo!.hashId)
   }
 
-  public static deleteInstance (chargingStation: ChargingStation): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return AutomaticTransactionGenerator.instances.delete(chargingStation.stationInfo!.hashId)
-  }
-
   public start (stopAbsoluteDuration?: boolean): void {
-    if (!checkChargingStation(this.chargingStation, this.logPrefix())) {
+    if (!checkChargingStationState(this.chargingStation, this.logPrefix())) {
       return
     }
     if (this.started) {
@@ -90,6 +92,21 @@ export class AutomaticTransactionGenerator {
     this.starting = false
   }
 
+  public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void {
+    if (!checkChargingStationState(this.chargingStation, this.logPrefix(connectorId))) {
+      return
+    }
+    if (!this.connectorsStatus.has(connectorId)) {
+      logger.error(`${this.logPrefix(connectorId)} starting on non existing connector`)
+      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
+    }
+    if (this.connectorsStatus.get(connectorId)?.start === false) {
+      this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
+    } else if (this.connectorsStatus.get(connectorId)?.start === true) {
+      logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
+    }
+  }
+
   public stop (): void {
     if (!this.started) {
       logger.warn(`${this.logPrefix()} is already stopped`)
@@ -105,25 +122,10 @@ export class AutomaticTransactionGenerator {
     this.stopping = false
   }
 
-  public startConnector (connectorId: number, stopAbsoluteDuration?: boolean): void {
-    if (!checkChargingStation(this.chargingStation, this.logPrefix(connectorId))) {
-      return
-    }
-    if (!this.connectorsStatus.has(connectorId)) {
-      logger.error(`${this.logPrefix(connectorId)} starting on non existing connector`)
-      throw new BaseError(`Connector ${connectorId} does not exist`)
-    }
-    if (this.connectorsStatus.get(connectorId)?.start === false) {
-      this.internalStartConnector(connectorId, stopAbsoluteDuration).catch(Constants.EMPTY_FUNCTION)
-    } else if (this.connectorsStatus.get(connectorId)?.start === true) {
-      logger.warn(`${this.logPrefix(connectorId)} is already started on connector`)
-    }
-  }
-
   public stopConnector (connectorId: number): void {
     if (!this.connectorsStatus.has(connectorId)) {
       logger.error(`${this.logPrefix(connectorId)} stopping on non existing connector`)
-      throw new BaseError(`Connector ${connectorId} does not exist`)
+      throw new BaseError(`Connector ${connectorId.toString()} does not exist`)
     }
     if (this.connectorsStatus.get(connectorId)?.start === true) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -133,44 +135,136 @@ export class AutomaticTransactionGenerator {
     }
   }
 
-  private startConnectors (stopAbsoluteDuration?: boolean): void {
-    if (
-      this.connectorsStatus.size > 0 &&
-      this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors()
-    ) {
-      this.connectorsStatus.clear()
-      this.initializeConnectorsStatus()
+  private canStartConnector (connectorId: number): boolean {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId
+        )} entered in transaction loop while the ATG stop date has been reached`
+      )
+      return false
     }
-    if (this.chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of this.chargingStation.evses) {
-        if (evseId > 0) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            this.startConnector(connectorId, stopAbsoluteDuration)
-          }
-        }
-      }
+    if (!this.chargingStation.inAcceptedState()) {
+      logger.error(
+        `${this.logPrefix(
+          connectorId
+        )} entered in transaction loop while the charging station is not in accepted state`
+      )
+      return false
+    }
+    if (!this.chargingStation.isChargingStationAvailable()) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId
+        )} entered in transaction loop while the charging station is unavailable`
+      )
+      return false
+    }
+    if (!this.chargingStation.isConnectorAvailable(connectorId)) {
+      logger.info(
+        `${this.logPrefix(
+          connectorId
+        )} entered in transaction loop while the connector ${connectorId.toString()} is unavailable`
+      )
+      return false
+    }
+    const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
+    if (connectorStatus?.transactionStarted === true) {
+      logger.info(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${this.logPrefix(connectorId)} entered in transaction loop while a transaction ${connectorStatus.transactionId?.toString()} is already started on connector ${connectorId.toString()}`
+      )
+      return false
+    }
+    return true
+  }
+
+  private getConnectorStatus (connectorId: number): Status {
+    const statusIndex = connectorId - 1
+    if (statusIndex < 0) {
+      logger.error(`${this.logPrefix(connectorId)} invalid connector id`)
+      throw new BaseError(`Invalid connector id ${connectorId.toString()}`)
+    }
+    let connectorStatus: Status | undefined
+    if (this.chargingStation.getAutomaticTransactionGeneratorStatuses()?.[statusIndex] != null) {
+      connectorStatus = clone<Status>(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.chargingStation.getAutomaticTransactionGeneratorStatuses()![statusIndex]
+      )
     } else {
-      for (const connectorId of this.chargingStation.connectors.keys()) {
-        if (connectorId > 0) {
-          this.startConnector(connectorId, stopAbsoluteDuration)
-        }
+      logger.warn(
+        `${this.logPrefix(
+          connectorId
+        )} no status found for connector #${connectorId.toString()} in charging station configuration file. New status will be created`
+      )
+    }
+    if (connectorStatus != null) {
+      connectorStatus.startDate = convertToDate(connectorStatus.startDate)
+      connectorStatus.lastRunDate = convertToDate(connectorStatus.lastRunDate)
+      connectorStatus.stopDate = convertToDate(connectorStatus.stopDate)
+      connectorStatus.stoppedDate = convertToDate(connectorStatus.stoppedDate)
+      if (
+        !this.started &&
+        (connectorStatus.start ||
+          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.enable !== true)
+      ) {
+        connectorStatus.start = false
       }
     }
+    return (
+      connectorStatus ?? {
+        acceptedAuthorizeRequests: 0,
+        acceptedStartTransactionRequests: 0,
+        acceptedStopTransactionRequests: 0,
+        authorizeRequests: 0,
+        rejectedAuthorizeRequests: 0,
+        rejectedStartTransactionRequests: 0,
+        rejectedStopTransactionRequests: 0,
+        skippedConsecutiveTransactions: 0,
+        skippedTransactions: 0,
+        start: false,
+        startTransactionRequests: 0,
+        stopTransactionRequests: 0,
+      }
+    )
   }
 
-  private stopConnectors (): void {
+  private getRequireAuthorize (): boolean {
+    return (
+      this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.requireAuthorize ?? true
+    )
+  }
+
+  private handleStartTransactionResponse (
+    connectorId: number,
+    startResponse: StartTransactionResponse
+  ): void {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    ++this.connectorsStatus.get(connectorId)!.startTransactionRequests
+    if (startResponse.idTagInfo.status === AuthorizationStatus.ACCEPTED) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ++this.connectorsStatus.get(connectorId)!.acceptedStartTransactionRequests
+    } else {
+      logger.warn(`${this.logPrefix(connectorId)} start transaction rejected`)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ++this.connectorsStatus.get(connectorId)!.rejectedStartTransactionRequests
+    }
+  }
+
+  private initializeConnectorsStatus (): void {
     if (this.chargingStation.hasEvses) {
       for (const [evseId, evseStatus] of this.chargingStation.evses) {
         if (evseId > 0) {
           for (const connectorId of evseStatus.connectors.keys()) {
-            this.stopConnector(connectorId)
+            this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
           }
         }
       }
     } else {
       for (const connectorId of this.chargingStation.connectors.keys()) {
         if (connectorId > 0) {
-          this.stopConnector(connectorId)
+          this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
         }
       }
     }
@@ -201,10 +295,12 @@ export class AutomaticTransactionGenerator {
       }
       const wait = secondsToMilliseconds(
         randomInt(
-          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
-            ?.minDelayBetweenTwoTransactions,
-          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
-            ?.maxDelayBetweenTwoTransactions
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!
+            .minDelayBetweenTwoTransactions,
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!
+            .maxDelayBetweenTwoTransactions
         )
       )
       logger.info(`${this.logPrefix(connectorId)} waiting for ${formatDurationMilliSeconds(wait)}`)
@@ -223,14 +319,17 @@ export class AutomaticTransactionGenerator {
           // Wait until end of transaction
           const waitTrxEnd = secondsToMilliseconds(
             randomInt(
-              this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.minDuration,
-              this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.maxDuration
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!.minDuration,
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              this.chargingStation.getAutomaticTransactionGeneratorConfiguration()!.maxDuration
             )
           )
           logger.info(
-            `${this.logPrefix(connectorId)} transaction started with id ${
-              this.chargingStation.getConnectorStatus(connectorId)?.transactionId
-            } and will stop in ${formatDurationMilliSeconds(waitTrxEnd)}`
+            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            `${this.logPrefix(connectorId)} transaction started with id ${this.chargingStation
+              .getConnectorStatus(connectorId)
+              ?.transactionId?.toString()} and will stop in ${formatDurationMilliSeconds(waitTrxEnd)}`
           )
           await sleep(waitTrxEnd)
           await this.stopTransaction(connectorId)
@@ -241,9 +340,12 @@ export class AutomaticTransactionGenerator {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         ++this.connectorsStatus.get(connectorId)!.skippedTransactions
         logger.info(
-          `${this.logPrefix(connectorId)} skipped consecutively ${
-            this.connectorsStatus.get(connectorId)?.skippedConsecutiveTransactions
-          }/${this.connectorsStatus.get(connectorId)?.skippedTransactions} transaction(s)`
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `${this.logPrefix(connectorId)} skipped consecutively ${this.connectorsStatus
+            .get(connectorId)
+            ?.skippedConsecutiveTransactions.toString()
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          }/${this.connectorsStatus.get(connectorId)?.skippedTransactions.toString()} transaction(s)`
         )
       }
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -262,9 +364,19 @@ export class AutomaticTransactionGenerator {
       )}`
     )
     logger.debug(
-      `${this.logPrefix(connectorId)} connector status: %j`,
+      `${this.logPrefix(connectorId)} stopped with connector status: %j`,
       this.connectorsStatus.get(connectorId)
     )
+    this.chargingStation.emit(ChargingStationEvents.updated)
+  }
+
+  private readonly logPrefix = (connectorId?: number): string => {
+    return logPrefix(
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      ` ${this.chargingStation.stationInfo?.chargingStationId} | ATG${
+        connectorId != null ? ` on connector #${connectorId.toString()}` : ''
+      }:`
+    )
   }
 
   private setStartConnectorStatus (
@@ -294,162 +406,34 @@ export class AutomaticTransactionGenerator {
     this.connectorsStatus.get(connectorId)!.skippedConsecutiveTransactions = 0
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     this.connectorsStatus.get(connectorId)!.start = true
+    this.chargingStation.emit(ChargingStationEvents.updated)
   }
 
-  private canStartConnector (connectorId: number): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) {
-      logger.info(
-        `${this.logPrefix(
-          connectorId
-        )} entered in transaction loop while the ATG stop date has been reached`
-      )
-      return false
-    }
-    if (!this.chargingStation.inAcceptedState()) {
-      logger.error(
-        `${this.logPrefix(
-          connectorId
-        )} entered in transaction loop while the charging station is not in accepted state`
-      )
-      return false
-    }
-    if (!this.chargingStation.isChargingStationAvailable()) {
-      logger.info(
-        `${this.logPrefix(
-          connectorId
-        )} entered in transaction loop while the charging station is unavailable`
-      )
-      return false
-    }
-    if (!this.chargingStation.isConnectorAvailable(connectorId)) {
-      logger.info(
-        `${this.logPrefix(
-          connectorId
-        )} entered in transaction loop while the connector ${connectorId} is unavailable`
-      )
-      return false
-    }
-    const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
-    if (connectorStatus?.transactionStarted === true) {
-      logger.info(
-        `${this.logPrefix(
-          connectorId
-        )} entered in transaction loop while a transaction ${connectorStatus.transactionId} is already started on connector ${connectorId}`
-      )
-      return false
-    }
-    return true
-  }
-
-  private async waitChargingStationAvailable (connectorId: number): Promise<void> {
-    let logged = false
-    while (!this.chargingStation.isChargingStationAvailable()) {
-      if (!logged) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId
-          )} transaction loop waiting for charging station to be available`
-        )
-        logged = true
-      }
-      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
-    }
-  }
-
-  private async waitConnectorAvailable (connectorId: number): Promise<void> {
-    let logged = false
-    while (!this.chargingStation.isConnectorAvailable(connectorId)) {
-      if (!logged) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId
-          )} transaction loop waiting for connector ${connectorId} to be available`
-        )
-        logged = true
-      }
-      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
-    }
-  }
-
-  private async waitRunningTransactionStopped (connectorId: number): Promise<void> {
-    const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
-    let logged = false
-    while (connectorStatus?.transactionStarted === true) {
-      if (!logged) {
-        logger.info(
-          `${this.logPrefix(
-            connectorId
-          )} transaction loop waiting for started transaction ${connectorStatus.transactionId} on connector ${connectorId} to be stopped`
-        )
-        logged = true
-      }
-      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
+  private startConnectors (stopAbsoluteDuration?: boolean): void {
+    if (
+      this.connectorsStatus.size > 0 &&
+      this.connectorsStatus.size !== this.chargingStation.getNumberOfConnectors()
+    ) {
+      this.connectorsStatus.clear()
+      this.initializeConnectorsStatus()
     }
-  }
-
-  private initializeConnectorsStatus (): void {
     if (this.chargingStation.hasEvses) {
       for (const [evseId, evseStatus] of this.chargingStation.evses) {
         if (evseId > 0) {
           for (const connectorId of evseStatus.connectors.keys()) {
-            this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
+            this.startConnector(connectorId, stopAbsoluteDuration)
           }
         }
       }
     } else {
       for (const connectorId of this.chargingStation.connectors.keys()) {
         if (connectorId > 0) {
-          this.connectorsStatus.set(connectorId, this.getConnectorStatus(connectorId))
+          this.startConnector(connectorId, stopAbsoluteDuration)
         }
       }
     }
   }
 
-  private getConnectorStatus (connectorId: number): Status {
-    const statusIndex = connectorId - 1
-    let connectorStatus: Status | undefined
-    if (this.chargingStation.getAutomaticTransactionGeneratorStatuses()?.[statusIndex] != null) {
-      connectorStatus = clone<Status>(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.chargingStation.getAutomaticTransactionGeneratorStatuses()![statusIndex]
-      )
-    } else if (this.chargingStation.getAutomaticTransactionGeneratorStatuses() != null) {
-      logger.warn(
-        `${this.logPrefix(connectorId)} no status found for connector #${connectorId} in charging station configuration file. New status will be created`
-      )
-    }
-    if (connectorStatus != null) {
-      connectorStatus.startDate = convertToDate(connectorStatus.startDate)
-      connectorStatus.lastRunDate = convertToDate(connectorStatus.lastRunDate)
-      connectorStatus.stopDate = convertToDate(connectorStatus.stopDate)
-      connectorStatus.stoppedDate = convertToDate(connectorStatus.stoppedDate)
-      if (
-        !this.started &&
-        (connectorStatus.start ||
-          this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.enable !== true)
-      ) {
-        connectorStatus.start = false
-      }
-    }
-    return (
-      connectorStatus ?? {
-        start: false,
-        authorizeRequests: 0,
-        acceptedAuthorizeRequests: 0,
-        rejectedAuthorizeRequests: 0,
-        startTransactionRequests: 0,
-        acceptedStartTransactionRequests: 0,
-        rejectedStartTransactionRequests: 0,
-        stopTransactionRequests: 0,
-        acceptedStopTransactionRequests: 0,
-        rejectedStopTransactionRequests: 0,
-        skippedConsecutiveTransactions: 0,
-        skippedTransactions: 0
-      }
-    )
-  }
-
   private async startTransaction (
     connectorId: number
   ): Promise<StartTransactionResponse | undefined> {
@@ -475,11 +459,11 @@ export class AutomaticTransactionGenerator {
           logger.info(startTransactionLogMsg)
           // Start transaction
           startResponse = await this.chargingStation.ocppRequestService.requestHandler<
-          Partial<StartTransactionRequest>,
-          StartTransactionResponse
+            Partial<StartTransactionRequest>,
+            StartTransactionResponse
           >(this.chargingStation, RequestCommand.START_TRANSACTION, {
             connectorId,
-            idTag
+            idTag,
           })
           this.handleStartTransactionResponse(connectorId, startResponse)
           PerformanceStatistics.endMeasure(measureId, beginId)
@@ -493,11 +477,11 @@ export class AutomaticTransactionGenerator {
       logger.info(startTransactionLogMsg)
       // Start transaction
       startResponse = await this.chargingStation.ocppRequestService.requestHandler<
-      Partial<StartTransactionRequest>,
-      StartTransactionResponse
+        Partial<StartTransactionRequest>,
+        StartTransactionResponse
       >(this.chargingStation, RequestCommand.START_TRANSACTION, {
         connectorId,
-        idTag
+        idTag,
       })
       this.handleStartTransactionResponse(connectorId, startResponse)
       PerformanceStatistics.endMeasure(measureId, beginId)
@@ -505,14 +489,34 @@ export class AutomaticTransactionGenerator {
     }
     logger.info(`${this.logPrefix(connectorId)} start transaction without an idTag`)
     startResponse = await this.chargingStation.ocppRequestService.requestHandler<
-    Partial<StartTransactionRequest>,
-    StartTransactionResponse
-    >(this.chargingStation, RequestCommand.START_TRANSACTION, { connectorId })
+      Partial<StartTransactionRequest>,
+      StartTransactionResponse
+    >(this.chargingStation, RequestCommand.START_TRANSACTION, {
+      connectorId,
+    })
     this.handleStartTransactionResponse(connectorId, startResponse)
     PerformanceStatistics.endMeasure(measureId, beginId)
     return startResponse
   }
 
+  private stopConnectors (): void {
+    if (this.chargingStation.hasEvses) {
+      for (const [evseId, evseStatus] of this.chargingStation.evses) {
+        if (evseId > 0) {
+          for (const connectorId of evseStatus.connectors.keys()) {
+            this.stopConnector(connectorId)
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.chargingStation.connectors.keys()) {
+        if (connectorId > 0) {
+          this.stopConnector(connectorId)
+        }
+      }
+    }
+  }
+
   private async stopTransaction (
     connectorId: number,
     reason = StopTransactionReason.LOCAL
@@ -522,9 +526,10 @@ export class AutomaticTransactionGenerator {
     let stopResponse: StopTransactionResponse | undefined
     if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
       logger.info(
-        `${this.logPrefix(connectorId)} stop transaction with id ${
-          this.chargingStation.getConnectorStatus(connectorId)?.transactionId
-        }`
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${this.logPrefix(connectorId)} stop transaction with id ${this.chargingStation
+          .getConnectorStatus(connectorId)
+          ?.transactionId?.toString()}`
       )
       stopResponse = await this.chargingStation.stopTransactionOnConnector(connectorId, reason)
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -540,7 +545,7 @@ export class AutomaticTransactionGenerator {
       const transactionId = this.chargingStation.getConnectorStatus(connectorId)?.transactionId
       logger.debug(
         `${this.logPrefix(connectorId)} stopping a not started transaction${
-          transactionId != null ? ` with id ${transactionId}` : ''
+          transactionId != null ? ` with id ${transactionId.toString()}` : ''
         }`
       )
     }
@@ -548,33 +553,48 @@ export class AutomaticTransactionGenerator {
     return stopResponse
   }
 
-  private getRequireAuthorize (): boolean {
-    return (
-      this.chargingStation.getAutomaticTransactionGeneratorConfiguration()?.requireAuthorize ?? true
-    )
+  private async waitChargingStationAvailable (connectorId: number): Promise<void> {
+    let logged = false
+    while (!this.chargingStation.isChargingStationAvailable()) {
+      if (!logged) {
+        logger.info(
+          `${this.logPrefix(
+            connectorId
+          )} transaction loop waiting for charging station to be available`
+        )
+        logged = true
+      }
+      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
+    }
   }
 
-  private readonly logPrefix = (connectorId?: number): string => {
-    return logPrefix(
-      ` ${this.chargingStation.stationInfo?.chargingStationId} | ATG${
-        connectorId != null ? ` on connector #${connectorId}` : ''
-      }:`
-    )
+  private async waitConnectorAvailable (connectorId: number): Promise<void> {
+    let logged = false
+    while (!this.chargingStation.isConnectorAvailable(connectorId)) {
+      if (!logged) {
+        logger.info(
+          `${this.logPrefix(
+            connectorId
+          )} transaction loop waiting for connector ${connectorId.toString()} to be available`
+        )
+        logged = true
+      }
+      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
+    }
   }
 
-  private handleStartTransactionResponse (
-    connectorId: number,
-    startResponse: StartTransactionResponse
-  ): void {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    ++this.connectorsStatus.get(connectorId)!.startTransactionRequests
-    if (startResponse.idTagInfo.status === AuthorizationStatus.ACCEPTED) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ++this.connectorsStatus.get(connectorId)!.acceptedStartTransactionRequests
-    } else {
-      logger.warn(`${this.logPrefix(connectorId)} start transaction rejected`)
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ++this.connectorsStatus.get(connectorId)!.rejectedStartTransactionRequests
+  private async waitRunningTransactionStopped (connectorId: number): Promise<void> {
+    const connectorStatus = this.chargingStation.getConnectorStatus(connectorId)
+    let logged = false
+    while (connectorStatus?.transactionStarted === true) {
+      if (!logged) {
+        logger.info(
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `${this.logPrefix(connectorId)} transaction loop waiting for started transaction ${connectorStatus.transactionId?.toString()} on connector ${connectorId.toString()} to be stopped`
+        )
+        logged = true
+      }
+      await sleep(Constants.DEFAULT_ATG_WAIT_TIME)
     }
   }
 }
index 5293a9a83c28a9a1791f166606d3ca275f2cbb67..b4f3633bc1109ad8345ff79d05006c222265ba7a 100644 (file)
@@ -1,14 +1,16 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
+import type { Worker } from 'node:worker_threads'
+
+import chalk from 'chalk'
 import { EventEmitter } from 'node:events'
 import { dirname, extname, join } from 'node:path'
 import process, { exit } from 'node:process'
 import { fileURLToPath } from 'node:url'
 import { isMainThread } from 'node:worker_threads'
-
-import chalk from 'chalk'
 import { availableParallelism, type MessageHandler } from 'poolifier'
-import type { Worker } from 'worker_threads'
+
+import type { AbstractUIServer } from './ui-server/AbstractUIServer.js'
 
 import { version } from '../../package.json'
 import { BaseError } from '../exception/index.js'
@@ -28,7 +30,7 @@ import {
   type StorageConfiguration,
   type TemplateStatistics,
   type UIServerConfiguration,
-  type WorkerConfiguration
+  type WorkerConfiguration,
 } from '../types/index.js'
 import {
   Configuration,
@@ -40,34 +42,67 @@ import {
   isAsyncFunction,
   isNotEmptyArray,
   logger,
-  logPrefix
+  logPrefix,
 } from '../utils/index.js'
 import { DEFAULT_ELEMENTS_PER_WORKER, type WorkerAbstract, WorkerFactory } from '../worker/index.js'
 import { buildTemplateName, waitChargingStationEvents } from './Helpers.js'
-import type { AbstractUIServer } from './ui-server/AbstractUIServer.js'
 import { UIServerFactory } from './ui-server/UIServerFactory.js'
 
 const moduleName = 'Bootstrap'
 
+/* eslint-disable perfectionist/sort-enums */
 enum exitCodes {
   succeeded = 0,
   missingChargingStationsConfiguration = 1,
   duplicateChargingStationTemplateUrls = 2,
   noChargingStationTemplates = 3,
-  gracefulShutdownError = 4
+  gracefulShutdownError = 4,
 }
+/* eslint-enable perfectionist/sort-enums */
 
 export class Bootstrap extends EventEmitter {
   private static instance: Bootstrap | null = null
-  private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo>
-  private readonly uiServer: AbstractUIServer
-  private storage?: Storage
-  private readonly templateStatistics: Map<string, TemplateStatistics>
-  private readonly version: string = version
+  public get numberOfChargingStationTemplates (): number {
+    return this.templateStatistics.size
+  }
+
+  public get numberOfConfiguredChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.configured,
+      0
+    )
+  }
+
+  public get numberOfProvisionedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.provisioned,
+      0
+    )
+  }
+
   private started: boolean
   private starting: boolean
   private stopping: boolean
+  private storage?: Storage
+  private readonly templateStatistics: Map<string, TemplateStatistics>
+  private readonly uiServer: AbstractUIServer
   private uiServerStarted: boolean
+  private readonly version: string = version
+  private workerImplementation?: WorkerAbstract<ChargingStationWorkerData, ChargingStationInfo>
+
+  private get numberOfAddedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.added,
+      0
+    )
+  }
+
+  private get numberOfStartedChargingStations (): number {
+    return [...this.templateStatistics.values()].reduce(
+      (accumulator, value) => accumulator + value.started,
+      0
+    )
+  }
 
   private constructor () {
     super()
@@ -97,37 +132,35 @@ export class Bootstrap extends EventEmitter {
   }
 
   public static getInstance (): Bootstrap {
-    if (Bootstrap.instance === null) {
-      Bootstrap.instance = new Bootstrap()
-    }
+    Bootstrap.instance ??= new Bootstrap()
     return Bootstrap.instance
   }
 
-  public get numberOfChargingStationTemplates (): number {
-    return this.templateStatistics.size
-  }
-
-  public get numberOfConfiguredChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.configured,
-      0
-    )
-  }
-
-  public get numberOfProvisionedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.provisioned,
-      0
-    )
-  }
-
-  public getState (): SimulatorState {
-    return {
-      version: this.version,
-      configuration: Configuration.getConfigurationData(),
-      started: this.started,
-      templateStatistics: this.templateStatistics
+  public async addChargingStation (
+    index: number,
+    templateFile: string,
+    options?: ChargingStationOptions
+  ): Promise<ChargingStationInfo | undefined> {
+    if (!this.started && !this.starting) {
+      throw new BaseError(
+        'Cannot add charging station while the charging stations simulator is not started'
+      )
     }
+    const stationInfo = await this.workerImplementation?.addElement({
+      index,
+      options,
+      templateFile: join(
+        dirname(fileURLToPath(import.meta.url)),
+        'assets',
+        'station-templates',
+        templateFile
+      ),
+    })
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))!
+    ++templateStatistics.added
+    templateStatistics.indexes.add(index)
+    return stationInfo
   }
 
   public getLastIndex (templateName: string): number {
@@ -147,18 +180,13 @@ export class Bootstrap extends EventEmitter {
     return this.storage?.getPerformanceStatistics()
   }
 
-  private get numberOfAddedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.added,
-      0
-    )
-  }
-
-  private get numberOfStartedChargingStations (): number {
-    return [...this.templateStatistics.values()].reduce(
-      (accumulator, value) => accumulator + value.started,
-      0
-    )
+  public getState (): SimulatorState {
+    return {
+      configuration: Configuration.getConfigurationData(),
+      started: this.started,
+      templateStatistics: this.templateStatistics,
+      version: this.version,
+    }
   }
 
   public async start (): Promise<void> {
@@ -178,7 +206,7 @@ export class Bootstrap extends EventEmitter {
         if (isAsyncFunction(this.workerImplementation?.start)) {
           await this.workerImplementation.start()
         } else {
-          (this.workerImplementation?.start as () => void)()
+          ;(this.workerImplementation?.start as () => void)()
         }
         const performanceStorageConfiguration =
           Configuration.getConfigurationSection<StorageConfiguration>(
@@ -225,15 +253,21 @@ export class Bootstrap extends EventEmitter {
         )
         console.info(
           chalk.green(
-            `Charging stations simulator ${
-              this.version
-            } started with ${this.numberOfConfiguredChargingStations} configured and ${this.numberOfProvisionedChargingStations} provisioned charging station(s) from ${this.numberOfChargingStationTemplates} charging station template(s) and ${
-              Configuration.workerDynamicPoolInUse() ? `${workerConfiguration.poolMinSize}/` : ''
-            }${this.workerImplementation?.size}${
-              Configuration.workerPoolInUse() ? `/${workerConfiguration.poolMaxSize}` : ''
+            `Charging stations simulator ${this.version} started with ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s) from ${this.numberOfChargingStationTemplates.toString()} charging station template(s) and ${
+              Configuration.workerDynamicPoolInUse()
+                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  `${workerConfiguration.poolMinSize?.toString()}/`
+                : ''
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }${this.workerImplementation?.size.toString()}${
+              Configuration.workerPoolInUse()
+                ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  `/${workerConfiguration.poolMaxSize?.toString()}`
+                : ''
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
             } worker(s) concurrently running in '${workerConfiguration.processType}' mode${
               this.workerImplementation?.maxElementsPerWorker != null
-                ? ` (${this.workerImplementation.maxElementsPerWorker} charging station(s) per worker)`
+                ? ` (${this.workerImplementation.maxElementsPerWorker.toString()} charging station(s) per worker)`
                 : ''
             }`
           )
@@ -286,46 +320,70 @@ export class Bootstrap extends EventEmitter {
     }
   }
 
-  private async restart (): Promise<void> {
-    await this.stop()
+  private gracefulShutdown (): void {
+    this.stop()
+      .then(() => {
+        console.info(chalk.green('Graceful shutdown'))
+        this.uiServer.stop()
+        this.uiServerStarted = false
+        this.waitChargingStationsStopped()
+          // eslint-disable-next-line promise/no-nesting
+          .then(() => {
+            return exit(exitCodes.succeeded)
+          })
+          // eslint-disable-next-line promise/no-nesting
+          .catch(() => {
+            exit(exitCodes.gracefulShutdownError)
+          })
+        return undefined
+      })
+      .catch((error: unknown) => {
+        console.error(chalk.red('Error while shutdowning charging stations simulator: '), error)
+        exit(exitCodes.gracefulShutdownError)
+      })
+  }
+
+  private initializeCounters (): void {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const stationTemplateUrls = Configuration.getStationTemplateUrls()!
+    if (isNotEmptyArray(stationTemplateUrls)) {
+      for (const stationTemplateUrl of stationTemplateUrls) {
+        const templateName = buildTemplateName(stationTemplateUrl.file)
+        this.templateStatistics.set(templateName, {
+          added: 0,
+          configured: stationTemplateUrl.numberOfStations,
+          indexes: new Set<number>(),
+          provisioned: stationTemplateUrl.provisionedNumberOfStations ?? 0,
+          started: 0,
+        })
+        this.uiServer.chargingStationTemplates.add(templateName)
+      }
+      if (this.templateStatistics.size !== stationTemplateUrls.length) {
+        console.error(
+          chalk.red(
+            "'stationTemplateUrls' contains duplicate entries, please check your configuration"
+          )
+        )
+        exit(exitCodes.duplicateChargingStationTemplateUrls)
+      }
+    } else {
+      console.error(
+        chalk.red("'stationTemplateUrls' not defined or empty, please check your configuration")
+      )
+      exit(exitCodes.missingChargingStationsConfiguration)
+    }
     if (
-      this.uiServerStarted &&
+      this.numberOfConfiguredChargingStations === 0 &&
       Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
         .enabled !== true
     ) {
-      this.uiServer.stop()
-      this.uiServerStarted = false
-    }
-    this.initializeCounters()
-    // FIXME: initialize worker implementation only if the worker section has changed
-    this.initializeWorkerImplementation(
-      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
-    )
-    await this.start()
-  }
-
-  private async waitChargingStationsStopped (): Promise<string> {
-    return await new Promise<string>((resolve, reject: (reason?: unknown) => void) => {
-      const waitTimeout = setTimeout(() => {
-        const timeoutMessage = `Timeout ${formatDurationMilliSeconds(
-          Constants.STOP_CHARGING_STATIONS_TIMEOUT
-        )} reached at stopping charging stations`
-        console.warn(chalk.yellow(timeoutMessage))
-        reject(new Error(timeoutMessage))
-      }, Constants.STOP_CHARGING_STATIONS_TIMEOUT)
-      waitChargingStationEvents(
-        this,
-        ChargingStationWorkerMessageEvents.stopped,
-        this.numberOfStartedChargingStations
+      console.error(
+        chalk.red(
+          "'stationTemplateUrls' has no charging station enabled and UI server is disabled, please check your configuration"
+        )
       )
-        .then(() => {
-          resolve('Charging stations stopped')
-        })
-        .catch(reject)
-        .finally(() => {
-          clearTimeout(waitTimeout)
-        })
-    })
+      exit(exitCodes.noChargingStationTemplates)
+    }
   }
 
   private initializeWorkerImplementation (workerConfiguration: WorkerConfiguration): void {
@@ -353,8 +411,8 @@ export class Bootstrap extends EventEmitter {
         elementsPerWorker = workerConfiguration.elementsPerWorker ?? DEFAULT_ELEMENTS_PER_WORKER
     }
     this.workerImplementation = WorkerFactory.getWorkerImplementation<
-    ChargingStationWorkerData,
-    ChargingStationInfo
+      ChargingStationWorkerData,
+      ChargingStationInfo
     >(
       join(
         dirname(fileURLToPath(import.meta.url)),
@@ -363,23 +421,29 @@ export class Bootstrap extends EventEmitter {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       workerConfiguration.processType!,
       {
-        workerStartDelay: workerConfiguration.startDelay,
         elementAddDelay: workerConfiguration.elementAddDelay,
+        elementsPerWorker,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         poolMaxSize: workerConfiguration.poolMaxSize!,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         poolMinSize: workerConfiguration.poolMinSize!,
-        elementsPerWorker,
         poolOptions: {
           messageHandler: this.messageHandler.bind(this) as MessageHandler<Worker>,
           ...(workerConfiguration.resourceLimits != null && {
-            workerOptions: { resourceLimits: workerConfiguration.resourceLimits }
-          })
-        }
+            workerOptions: {
+              resourceLimits: workerConfiguration.resourceLimits,
+            },
+          }),
+        },
+        workerStartDelay: workerConfiguration.startDelay,
       }
     )
   }
 
+  private readonly logPrefix = (): string => {
+    return logPrefix(' Bootstrap |')
+  }
+
   private messageHandler (
     msg: ChargingStationWorkerMessage<ChargingStationWorkerMessageData>
   ): void {
@@ -395,7 +459,7 @@ export class Bootstrap extends EventEmitter {
     if (msg['uuid'] != null) {
       return
     }
-    const { event, data } = msg
+    const { data, event } = msg
     try {
       switch (event) {
         case ChargingStationWorkerMessageEvents.added:
@@ -404,6 +468,9 @@ export class Bootstrap extends EventEmitter {
         case ChargingStationWorkerMessageEvents.deleted:
           this.emit(ChargingStationWorkerMessageEvents.deleted, data)
           break
+        case ChargingStationWorkerMessageEvents.performanceStatistics:
+          this.emit(ChargingStationWorkerMessageEvents.performanceStatistics, data)
+          break
         case ChargingStationWorkerMessageEvents.started:
           this.emit(ChargingStationWorkerMessageEvents.started, data)
           break
@@ -413,12 +480,13 @@ export class Bootstrap extends EventEmitter {
         case ChargingStationWorkerMessageEvents.updated:
           this.emit(ChargingStationWorkerMessageEvents.updated, data)
           break
-        case ChargingStationWorkerMessageEvents.performanceStatistics:
-          this.emit(ChargingStationWorkerMessageEvents.performanceStatistics, data)
-          break
         default:
           throw new BaseError(
-            `Unknown charging station worker message event: '${event}' received with data: ${JSON.stringify(data, undefined, 2)}`
+            `Unknown charging station worker message event: '${event}' received with data: ${JSON.stringify(
+              data,
+              undefined,
+              2
+            )}`
           )
       }
     } catch (error) {
@@ -429,14 +497,56 @@ export class Bootstrap extends EventEmitter {
     }
   }
 
+  private async restart (): Promise<void> {
+    await this.stop()
+    if (
+      this.uiServerStarted &&
+      Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
+        .enabled !== true
+    ) {
+      this.uiServer.stop()
+      this.uiServerStarted = false
+    }
+    this.initializeCounters()
+    // FIXME: initialize worker implementation only if the worker section has changed
+    this.initializeWorkerImplementation(
+      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
+    )
+    await this.start()
+  }
+
+  private async waitChargingStationsStopped (): Promise<string> {
+    return await new Promise<string>((resolve, reject: (reason?: unknown) => void) => {
+      const waitTimeout = setTimeout(() => {
+        const timeoutMessage = `Timeout ${formatDurationMilliSeconds(
+          Constants.STOP_CHARGING_STATIONS_TIMEOUT
+        )} reached at stopping charging stations`
+        console.warn(chalk.yellow(timeoutMessage))
+        reject(new Error(timeoutMessage))
+      }, Constants.STOP_CHARGING_STATIONS_TIMEOUT)
+      waitChargingStationEvents(
+        this,
+        ChargingStationWorkerMessageEvents.stopped,
+        this.numberOfStartedChargingStations
+      )
+        .then(events => {
+          resolve('Charging stations stopped')
+          return events
+        })
+        .finally(() => {
+          clearTimeout(waitTimeout)
+        })
+        .catch(reject)
+    })
+  }
+
   private readonly workerEventAdded = (data: ChargingStationData): void => {
     this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
     logger.info(
       `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) added (${
-        this.numberOfAddedChargingStations
-      } added from ${this.numberOfConfiguredChargingStations} configured and ${this.numberOfProvisionedChargingStations} provisioned charging station(s))`
+      } (hashId: ${data.stationInfo.hashId}) added (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
     )
   }
 
@@ -448,23 +558,36 @@ export class Bootstrap extends EventEmitter {
     templateStatistics.indexes.delete(data.stationInfo.templateIndex)
     logger.info(
       `${this.logPrefix()} ${moduleName}.workerEventDeleted: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) deleted (${
-        this.numberOfAddedChargingStations
-      } added from ${this.numberOfConfiguredChargingStations} configured and ${this.numberOfProvisionedChargingStations} provisioned charging station(s))`
+      } (hashId: ${data.stationInfo.hashId}) deleted (${this.numberOfAddedChargingStations.toString()} added from ${this.numberOfConfiguredChargingStations.toString()} configured and ${this.numberOfProvisionedChargingStations.toString()} provisioned charging station(s))`
     )
   }
 
+  private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
+    // eslint-disable-next-line @typescript-eslint/unbound-method
+    if (isAsyncFunction(this.storage?.storePerformanceStatistics)) {
+      ;(
+        this.storage.storePerformanceStatistics as (
+          performanceStatistics: Statistics
+        ) => Promise<void>
+      )(data).catch(Constants.EMPTY_FUNCTION)
+    } else {
+      ;(this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)(
+        data
+      )
+    }
+  }
+
   private readonly workerEventStarted = (data: ChargingStationData): void => {
     this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     ++this.templateStatistics.get(data.stationInfo.templateName)!.started
     logger.info(
       `${this.logPrefix()} ${moduleName}.workerEventStarted: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) started (${
-        this.numberOfStartedChargingStations
-      } started from ${this.numberOfAddedChargingStations} added charging station(s))`
+      } (hashId: ${data.stationInfo.hashId}) started (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
     )
   }
 
@@ -474,123 +597,13 @@ export class Bootstrap extends EventEmitter {
     --this.templateStatistics.get(data.stationInfo.templateName)!.started
     logger.info(
       `${this.logPrefix()} ${moduleName}.workerEventStopped: Charging station ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         data.stationInfo.chargingStationId
-      } (hashId: ${data.stationInfo.hashId}) stopped (${
-        this.numberOfStartedChargingStations
-      } started from ${this.numberOfAddedChargingStations} added charging station(s))`
+      } (hashId: ${data.stationInfo.hashId}) stopped (${this.numberOfStartedChargingStations.toString()} started from ${this.numberOfAddedChargingStations.toString()} added charging station(s))`
     )
   }
 
   private readonly workerEventUpdated = (data: ChargingStationData): void => {
     this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
   }
-
-  private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
-    // eslint-disable-next-line @typescript-eslint/unbound-method
-    if (isAsyncFunction(this.storage?.storePerformanceStatistics)) {
-      (
-        this.storage.storePerformanceStatistics as (
-          performanceStatistics: Statistics
-        ) => Promise<void>
-      )(data).catch(Constants.EMPTY_FUNCTION)
-    } else {
-      (this.storage?.storePerformanceStatistics as (performanceStatistics: Statistics) => void)(
-        data
-      )
-    }
-  }
-
-  private initializeCounters (): void {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const stationTemplateUrls = Configuration.getStationTemplateUrls()!
-    if (isNotEmptyArray(stationTemplateUrls)) {
-      for (const stationTemplateUrl of stationTemplateUrls) {
-        const templateName = buildTemplateName(stationTemplateUrl.file)
-        this.templateStatistics.set(templateName, {
-          configured: stationTemplateUrl.numberOfStations,
-          provisioned: stationTemplateUrl.provisionedNumberOfStations ?? 0,
-          added: 0,
-          started: 0,
-          indexes: new Set<number>()
-        })
-        this.uiServer.chargingStationTemplates.add(templateName)
-      }
-      if (this.templateStatistics.size !== stationTemplateUrls.length) {
-        console.error(
-          chalk.red(
-            "'stationTemplateUrls' contains duplicate entries, please check your configuration"
-          )
-        )
-        exit(exitCodes.duplicateChargingStationTemplateUrls)
-      }
-    } else {
-      console.error(
-        chalk.red("'stationTemplateUrls' not defined or empty, please check your configuration")
-      )
-      exit(exitCodes.missingChargingStationsConfiguration)
-    }
-    if (
-      this.numberOfConfiguredChargingStations === 0 &&
-      Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
-        .enabled !== true
-    ) {
-      console.error(
-        chalk.red(
-          "'stationTemplateUrls' has no charging station enabled and UI server is disabled, please check your configuration"
-        )
-      )
-      exit(exitCodes.noChargingStationTemplates)
-    }
-  }
-
-  public async addChargingStation (
-    index: number,
-    templateFile: string,
-    options?: ChargingStationOptions
-  ): Promise<ChargingStationInfo | undefined> {
-    if (!this.started && !this.starting) {
-      throw new BaseError(
-        'Cannot add charging station while the charging stations simulator is not started'
-      )
-    }
-    const stationInfo = await this.workerImplementation?.addElement({
-      index,
-      templateFile: join(
-        dirname(fileURLToPath(import.meta.url)),
-        'assets',
-        'station-templates',
-        templateFile
-      ),
-      options
-    })
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const templateStatistics = this.templateStatistics.get(buildTemplateName(templateFile))!
-    ++templateStatistics.added
-    templateStatistics.indexes.add(index)
-    return stationInfo
-  }
-
-  private gracefulShutdown (): void {
-    this.stop()
-      .then(() => {
-        console.info(chalk.green('Graceful shutdown'))
-        this.uiServer.stop()
-        this.uiServerStarted = false
-        this.waitChargingStationsStopped()
-          .then(() => {
-            exit(exitCodes.succeeded)
-          })
-          .catch(() => {
-            exit(exitCodes.gracefulShutdownError)
-          })
-      })
-      .catch((error: unknown) => {
-        console.error(chalk.red('Error while shutdowning charging stations simulator: '), error)
-        exit(exitCodes.gracefulShutdownError)
-      })
-  }
-
-  private readonly logPrefix = (): string => {
-    return logPrefix(' Bootstrap |')
-  }
 }
index 978bcbd314e6ecbd04d64d53062d904b5b14d168..5dca7b722446827b26355143e3206656f5ab1f80 100644 (file)
@@ -1,14 +1,12 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
-import { createHash, randomInt } from 'node:crypto'
+import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns'
+import { hash, randomInt } from 'node:crypto'
 import { EventEmitter } from 'node:events'
 import { existsSync, type FSWatcher, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { URL } from 'node:url'
 import { parentPort } from 'node:worker_threads'
-
-import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns'
-import { mergeDeepRight, once } from 'rambda'
 import { type RawData, WebSocket } from 'ws'
 
 import { BaseError, OCPPError } from '../exception/index.js'
@@ -64,7 +62,7 @@ import {
   type Voltage,
   WebSocketCloseEventStatusCode,
   type WSError,
-  type WsOptions
+  type WsOptions,
 } from '../types/index.js'
 import {
   ACElectricUtils,
@@ -90,15 +88,18 @@ import {
   formatDurationSeconds,
   getWebSocketCloseEventStatusString,
   handleFileException,
+  isEmpty,
   isNotEmptyArray,
   isNotEmptyString,
   logger,
   logPrefix,
+  mergeDeepRight,
   min,
+  once,
   roundTo,
   secureRandom,
   sleep,
-  watchJsonFile
+  watchJsonFile,
 } from '../utils/index.js'
 import { AutomaticTransactionGenerator } from './AutomaticTransactionGenerator.js'
 import { ChargingStationWorkerBroadcastChannel } from './broadcast-channel/ChargingStationWorkerBroadcastChannel.js'
@@ -106,12 +107,12 @@ import {
   addConfigurationKey,
   deleteConfigurationKey,
   getConfigurationKey,
-  setConfigurationKeyValue
+  setConfigurationKeyValue,
 } from './ConfigurationKeyUtils.js'
 import {
   buildConnectorsMap,
   buildTemplateName,
-  checkChargingStation,
+  checkChargingStationState,
   checkConfiguration,
   checkConnectorsConfiguration,
   checkStationInfoConnectorStatus,
@@ -120,8 +121,9 @@ import {
   createSerialNumber,
   getAmperageLimitationUnitDivider,
   getBootConnectorStatus,
-  getChargingStationConnectorChargingProfilesPowerLimit,
+  getChargingStationChargingProfilesLimit,
   getChargingStationId,
+  getConnectorChargingProfilesLimit,
   getDefaultVoltageOut,
   getHashId,
   getIdTagsFile,
@@ -131,10 +133,12 @@ import {
   hasFeatureProfile,
   hasReservationExpired,
   initializeConnectorsMapStatus,
+  prepareConnectorStatus,
   propagateSerialNumber,
   setChargingStationOptions,
   stationTemplateToStationInfo,
-  warnTemplateKeysDeprecation
+  validateStationInfo,
+  warnTemplateKeysDeprecation,
 } from './Helpers.js'
 import { IdTagsCache } from './IdTagsCache.js'
 import {
@@ -149,46 +153,68 @@ import {
   OCPP20ResponseService,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
-  sendAndSetConnectorStatus
+  sendAndSetConnectorStatus,
 } from './ocpp/index.js'
 import { SharedLRUCache } from './SharedLRUCache.js'
 
 export class ChargingStation extends EventEmitter {
-  public readonly index: number
-  public readonly templateFile: string
-  public stationInfo?: ChargingStationInfo
-  public started: boolean
-  public starting: boolean
-  public idTagsCache: IdTagsCache
   public automaticTransactionGenerator?: AutomaticTransactionGenerator
-  public ocppConfiguration?: ChargingStationOcppConfiguration
-  public wsConnection: WebSocket | null
+  public bootNotificationRequest?: BootNotificationRequest
+  public bootNotificationResponse?: BootNotificationResponse
   public readonly connectors: Map<number, ConnectorStatus>
   public readonly evses: Map<number, EvseStatus>
-  public readonly requests: Map<string, CachedRequest>
-  public performanceStatistics?: PerformanceStatistics
   public heartbeatSetInterval?: NodeJS.Timeout
+  public idTagsCache: IdTagsCache
+  public readonly index: number
+  public ocppConfiguration?: ChargingStationOcppConfiguration
   public ocppRequestService!: OCPPRequestService
-  public bootNotificationRequest?: BootNotificationRequest
-  public bootNotificationResponse?: BootNotificationResponse
+  public performanceStatistics?: PerformanceStatistics
   public powerDivider?: number
-  private stopping: boolean
+  public readonly requests: Map<string, CachedRequest>
+  public started: boolean
+  public starting: boolean
+  public stationInfo?: ChargingStationInfo
+  public readonly templateFile: string
+  public wsConnection: null | WebSocket
+
+  public get hasEvses (): boolean {
+    return isEmpty(this.connectors) && this.evses.size > 0
+  }
+
+  public get wsConnectionUrl (): URL {
+    const wsConnectionBaseUrlStr = `${
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value)
+        ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value
+        : this.configuredSupervisionUrl.href
+    }`
+    return new URL(
+      `${wsConnectionBaseUrlStr}${
+        !wsConnectionBaseUrlStr.endsWith('/') ? '/' : ''
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }${this.stationInfo?.chargingStationId}`
+    )
+  }
+
+  private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
+  private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel
   private configurationFile!: string
   private configurationFileHash!: string
+  private configuredSupervisionUrl!: URL
   private connectorsConfigurationHash!: string
   private evsesConfigurationHash!: string
-  private automaticTransactionGeneratorConfiguration?: AutomaticTransactionGeneratorConfiguration
+  private flushingMessageBuffer: boolean
+  private flushMessageBufferSetInterval?: NodeJS.Timeout
+  private readonly messageQueue: string[]
   private ocppIncomingRequestService!: OCPPIncomingRequestService
-  private readonly messageBuffer: Set<string>
-  private configuredSupervisionUrl!: URL
-  private wsConnectionRetried: boolean
-  private wsConnectionRetryCount: number
-  private templateFileWatcher?: FSWatcher
-  private templateFileHash!: string
   private readonly sharedLRUCache: SharedLRUCache
+  private stopping: boolean
+  private templateFileHash!: string
+  private templateFileWatcher?: FSWatcher
+  private wsConnectionRetryCount: number
   private wsPingSetInterval?: NodeJS.Timeout
-  private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel
-  private flushMessageBufferSetInterval?: NodeJS.Timeout
 
   constructor (index: number, templateFile: string, options?: ChargingStationOptions) {
     super()
@@ -196,14 +222,14 @@ export class ChargingStation extends EventEmitter {
     this.starting = false
     this.stopping = false
     this.wsConnection = null
-    this.wsConnectionRetried = false
     this.wsConnectionRetryCount = 0
     this.index = index
     this.templateFile = templateFile
     this.connectors = new Map<number, ConnectorStatus>()
     this.evses = new Map<number, EvseStatus>()
     this.requests = new Map<string, CachedRequest>()
-    this.messageBuffer = new Set<string>()
+    this.flushingMessageBuffer = false
+    this.messageQueue = [] as string[]
     this.sharedLRUCache = SharedLRUCache.getInstance()
     this.idTagsCache = IdTagsCache.getInstance()
     this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this)
@@ -225,16 +251,21 @@ export class ChargingStation extends EventEmitter {
     })
     this.on(ChargingStationEvents.accepted, () => {
       this.startMessageSequence(
-        this.wsConnectionRetried
+        this.wsConnectionRetryCount > 0
           ? true
           : this.getAutomaticTransactionGeneratorConfiguration()?.stopAbsoluteDuration
       ).catch((error: unknown) => {
         logger.error(`${this.logPrefix()} Error while starting the message sequence:`, error)
       })
-      this.wsConnectionRetried = false
+      this.wsConnectionRetryCount = 0
     })
     this.on(ChargingStationEvents.rejected, () => {
-      this.wsConnectionRetried = false
+      this.wsConnectionRetryCount = 0
+    })
+    this.on(ChargingStationEvents.connected, () => {
+      if (this.wsPingSetInterval == null) {
+        this.startWebSocketPing()
+      }
     })
     this.on(ChargingStationEvents.disconnected, () => {
       try {
@@ -256,143 +287,122 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  public get hasEvses (): boolean {
-    return this.connectors.size === 0 && this.evses.size > 0
-  }
-
-  public get wsConnectionUrl (): URL {
-    const wsConnectionBaseUrlStr = `${
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value)
-        ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey)?.value
-        : this.configuredSupervisionUrl.href
-    }`
-    return new URL(
-      `${wsConnectionBaseUrlStr}${!wsConnectionBaseUrlStr.endsWith('/') ? '/' : ''}${this.stationInfo?.chargingStationId}`
-    )
-  }
-
-  public logPrefix = (): string => {
-    if (
-      this instanceof ChargingStation &&
-      this.stationInfo != null &&
-      isNotEmptyString(this.stationInfo.chargingStationId)
-    ) {
-      return logPrefix(` ${this.stationInfo.chargingStationId} |`)
-    }
-    let stationTemplate: ChargingStationTemplate | undefined
-    try {
-      stationTemplate = JSON.parse(
-        readFileSync(this.templateFile, 'utf8')
-      ) as ChargingStationTemplate
-    } catch {
-      // Ignore
+  public async addReservation (reservation: Reservation): Promise<void> {
+    const reservationFound = this.getReservationBy('reservationId', reservation.reservationId)
+    if (reservationFound != null) {
+      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING)
     }
-    return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`)
-  }
-
-  public hasIdTags (): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo!)!))
-  }
-
-  public getNumberOfPhases (stationInfo?: ChargingStationInfo): number {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const localStationInfo = stationInfo ?? this.stationInfo!
-    switch (this.getCurrentOutType(stationInfo)) {
-      case CurrentType.AC:
-        return localStationInfo.numberOfPhases ?? 3
-      case CurrentType.DC:
-        return 0
-    }
-  }
-
-  public isWebSocketConnectionOpened (): boolean {
-    return this.wsConnection?.readyState === WebSocket.OPEN
-  }
-
-  public inUnknownState (): boolean {
-    return this.bootNotificationResponse?.status == null
-  }
-
-  public inPendingState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING
-  }
-
-  public inAcceptedState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED
-  }
-
-  public inRejectedState (): boolean {
-    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED
+    this.getConnectorStatus(reservation.connectorId)!.reservation = reservation
+    await sendAndSetConnectorStatus(
+      this,
+      reservation.connectorId,
+      ConnectorStatusEnum.Reserved,
+      undefined,
+      { send: reservation.connectorId !== 0 }
+    )
   }
 
-  public isRegistered (): boolean {
-    return !this.inUnknownState() && (this.inAcceptedState() || this.inPendingState())
+  public bufferMessage (message: string): void {
+    this.messageQueue.push(message)
+    this.setIntervalFlushMessageBuffer()
   }
 
-  public isChargingStationAvailable (): boolean {
-    return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative
+  public closeWSConnection (): void {
+    if (this.isWebSocketConnectionOpened()) {
+      this.wsConnection?.close()
+      this.wsConnection = null
+    }
   }
 
-  public hasConnector (connectorId: number): boolean {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        if (evseStatus.connectors.has(connectorId)) {
-          return true
-        }
-      }
-      return false
+  public async delete (deleteConfiguration = true): Promise<void> {
+    if (this.started) {
+      await this.stop()
     }
-    return this.connectors.has(connectorId)
+    AutomaticTransactionGenerator.deleteInstance(this)
+    PerformanceStatistics.deleteInstance(this.stationInfo?.hashId)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!)
+    this.requests.clear()
+    this.connectors.clear()
+    this.evses.clear()
+    this.templateFileWatcher?.unref()
+    deleteConfiguration && rmSync(this.configurationFile, { force: true })
+    this.chargingStationWorkerBroadcastChannel.unref()
+    this.emit(ChargingStationEvents.deleted)
+    this.removeAllListeners()
   }
 
-  public isConnectorAvailable (connectorId: number): boolean {
-    return (
-      connectorId > 0 &&
-      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
+  public getAuthorizeRemoteTxRequests (): boolean {
+    const authorizeRemoteTxRequests = getConfigurationKey(
+      this,
+      StandardParametersKey.AuthorizeRemoteTxRequests
     )
+    return authorizeRemoteTxRequests != null
+      ? convertToBoolean(authorizeRemoteTxRequests.value)
+      : false
   }
 
-  public getNumberOfConnectors (): number {
-    if (this.hasEvses) {
-      let numberOfConnectors = 0
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId > 0) {
-          numberOfConnectors += evseStatus.connectors.size
-        }
+  public getAutomaticTransactionGeneratorConfiguration ():
+    | AutomaticTransactionGeneratorConfiguration
+    | undefined {
+    if (this.automaticTransactionGeneratorConfiguration == null) {
+      let automaticTransactionGeneratorConfiguration:
+        | AutomaticTransactionGeneratorConfiguration
+        | undefined
+      const stationTemplate = this.getTemplateFromFile()
+      const stationConfiguration = this.getConfigurationFromFile()
+      if (
+        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
+        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+        stationConfiguration?.automaticTransactionGenerator != null
+      ) {
+        automaticTransactionGeneratorConfiguration =
+          stationConfiguration.automaticTransactionGenerator
+      } else {
+        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator
+      }
+      this.automaticTransactionGeneratorConfiguration = {
+        ...Constants.DEFAULT_ATG_CONFIGURATION,
+        ...automaticTransactionGeneratorConfiguration,
       }
-      return numberOfConnectors
     }
-    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size
+    return this.automaticTransactionGeneratorConfiguration
   }
 
-  public getNumberOfEvses (): number {
-    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size
+  public getAutomaticTransactionGeneratorStatuses (): Status[] | undefined {
+    return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses
   }
 
-  public getConnectorStatus (connectorId: number): ConnectorStatus | undefined {
-    if (this.hasEvses) {
+  public getConnectorIdByTransactionId (transactionId: number | undefined): number | undefined {
+    if (transactionId == null) {
+      return undefined
+    } else if (this.hasEvses) {
       for (const evseStatus of this.evses.values()) {
-        if (evseStatus.connectors.has(connectorId)) {
-          return evseStatus.connectors.get(connectorId)
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorId
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
+          return connectorId
         }
       }
-      return undefined
     }
-    return this.connectors.get(connectorId)
   }
 
   public getConnectorMaximumAvailablePower (connectorId: number): number {
-    let connectorAmperageLimitationPowerLimit: number | undefined
+    let connectorAmperageLimitationLimit: number | undefined
     const amperageLimitation = this.getAmperageLimitation()
     if (
       amperageLimitation != null &&
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       amperageLimitation < this.stationInfo!.maximumAmperage!
     ) {
-      connectorAmperageLimitationPowerLimit =
+      connectorAmperageLimitationLimit =
         (this.stationInfo?.currentOutType === CurrentType.AC
           ? ACElectricUtils.powerTotal(
             this.getNumberOfPhases(),
@@ -408,42 +418,104 @@ export class ChargingStation extends EventEmitter {
     }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const connectorMaximumPower = this.stationInfo!.maximumPower! / this.powerDivider!
-    const connectorChargingProfilesPowerLimit =
-      getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId)
-    return min(
-      isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower,
+    const chargingStationChargingProfilesLimit =
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      isNaN(connectorAmperageLimitationPowerLimit!)
+      getChargingStationChargingProfilesLimit(this)! / this.powerDivider!
+    const connectorChargingProfilesLimit = getConnectorChargingProfilesLimit(this, connectorId)
+    return min(
+      Number.isNaN(connectorMaximumPower) ? Number.POSITIVE_INFINITY : connectorMaximumPower,
+      connectorAmperageLimitationLimit == null || Number.isNaN(connectorAmperageLimitationLimit)
         ? Number.POSITIVE_INFINITY
-        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        connectorAmperageLimitationPowerLimit!,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      isNaN(connectorChargingProfilesPowerLimit!)
+        : connectorAmperageLimitationLimit,
+      Number.isNaN(chargingStationChargingProfilesLimit)
         ? Number.POSITIVE_INFINITY
-        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        connectorChargingProfilesPowerLimit!
+        : chargingStationChargingProfilesLimit,
+      connectorChargingProfilesLimit == null || Number.isNaN(connectorChargingProfilesLimit)
+        ? Number.POSITIVE_INFINITY
+        : connectorChargingProfilesLimit
     )
   }
 
-  public getTransactionIdTag (transactionId: number): string | undefined {
+  public getConnectorStatus (connectorId: number): ConnectorStatus | undefined {
     if (this.hasEvses) {
       for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorStatus.transactionIdTag
-          }
-        }
-      }
-    } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return this.getConnectorStatus(connectorId)?.transactionIdTag
+        if (evseStatus.connectors.has(connectorId)) {
+          return evseStatus.connectors.get(connectorId)
         }
       }
+      return undefined
     }
+    return this.connectors.get(connectorId)
   }
 
-  public getNumberOfRunningTransactions (): number {
+  public getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number {
+    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded)
+  }
+
+  public getEnergyActiveImportRegisterByTransactionId (
+    transactionId: number | undefined,
+    rounded = false
+  ): number {
+    return this.getEnergyActiveImportRegister(
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!),
+      rounded
+    )
+  }
+
+  public getHeartbeatInterval (): number {
+    const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
+    if (HeartbeatInterval != null) {
+      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value))
+    }
+    const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)
+    if (HeartBeatInterval != null) {
+      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value))
+    }
+    this.stationInfo?.autoRegister === false &&
+      logger.warn(
+        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${Constants.DEFAULT_HEARTBEAT_INTERVAL.toString()}`
+      )
+    return Constants.DEFAULT_HEARTBEAT_INTERVAL
+  }
+
+  public getLocalAuthListEnabled (): boolean {
+    const localAuthListEnabled = getConfigurationKey(
+      this,
+      StandardParametersKey.LocalAuthListEnabled
+    )
+    return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false
+  }
+
+  public getNumberOfConnectors (): number {
+    if (this.hasEvses) {
+      let numberOfConnectors = 0
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId > 0) {
+          numberOfConnectors += evseStatus.connectors.size
+        }
+      }
+      return numberOfConnectors
+    }
+    return this.connectors.has(0) ? this.connectors.size - 1 : this.connectors.size
+  }
+
+  public getNumberOfEvses (): number {
+    return this.evses.has(0) ? this.evses.size - 1 : this.evses.size
+  }
+
+  public getNumberOfPhases (stationInfo?: ChargingStationInfo): number {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const localStationInfo = stationInfo ?? this.stationInfo!
+    switch (this.getCurrentOutType(stationInfo)) {
+      case CurrentType.AC:
+        return localStationInfo.numberOfPhases ?? 3
+      case CurrentType.DC:
+        return 0
+    }
+  }
+
+  public getNumberOfRunningTransactions (): number {
     let numberOfRunningTransactions = 0
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
@@ -466,224 +538,274 @@ export class ChargingStation extends EventEmitter {
     return numberOfRunningTransactions
   }
 
-  public getConnectorIdByTransactionId (transactionId: number | undefined): number | undefined {
-    if (transactionId == null) {
-      return undefined
-    } else if (this.hasEvses) {
+  public getReservationBy (
+    filterKey: ReservationKey,
+    value: number | string
+  ): Reservation | undefined {
+    if (this.hasEvses) {
       for (const evseStatus of this.evses.values()) {
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionId === transactionId) {
-            return connectorId
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.reservation?.[filterKey] === value) {
+            return connectorStatus.reservation
           }
         }
       }
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
-          return connectorId
+      for (const connectorStatus of this.connectors.values()) {
+        if (connectorStatus.reservation?.[filterKey] === value) {
+          return connectorStatus.reservation
         }
       }
     }
   }
 
-  public getEnergyActiveImportRegisterByTransactionId (
-    transactionId: number | undefined,
-    rounded = false
-  ): number {
-    return this.getEnergyActiveImportRegister(
+  public getReserveConnectorZeroSupported (): boolean {
+    return convertToBoolean(
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.getConnectorStatus(this.getConnectorIdByTransactionId(transactionId)!),
-      rounded
+      getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value
     )
   }
 
-  public getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number {
-    return this.getEnergyActiveImportRegister(this.getConnectorStatus(connectorId), rounded)
+  public getTransactionIdTag (transactionId: number): string | undefined {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        for (const connectorStatus of evseStatus.connectors.values()) {
+          if (connectorStatus.transactionId === transactionId) {
+            return connectorStatus.transactionIdTag
+          }
+        }
+      }
+    } else {
+      for (const connectorId of this.connectors.keys()) {
+        if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) {
+          return this.getConnectorStatus(connectorId)?.transactionIdTag
+        }
+      }
+    }
   }
 
-  public getAuthorizeRemoteTxRequests (): boolean {
-    const authorizeRemoteTxRequests = getConfigurationKey(
-      this,
-      StandardParametersKey.AuthorizeRemoteTxRequests
-    )
-    return authorizeRemoteTxRequests != null
-      ? convertToBoolean(authorizeRemoteTxRequests.value)
-      : false
+  public hasConnector (connectorId: number): boolean {
+    if (this.hasEvses) {
+      for (const evseStatus of this.evses.values()) {
+        if (evseStatus.connectors.has(connectorId)) {
+          return true
+        }
+      }
+      return false
+    }
+    return this.connectors.has(connectorId)
   }
 
-  public getLocalAuthListEnabled (): boolean {
-    const localAuthListEnabled = getConfigurationKey(
-      this,
-      StandardParametersKey.LocalAuthListEnabled
-    )
-    return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false
+  public hasIdTags (): boolean {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo!)!))
   }
 
-  public getHeartbeatInterval (): number {
-    const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval)
-    if (HeartbeatInterval != null) {
-      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value))
-    }
-    const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval)
-    if (HeartBeatInterval != null) {
-      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value))
-    }
-    this.stationInfo?.autoRegister === false &&
-      logger.warn(
-        `${this.logPrefix()} Heartbeat interval configuration key not set, using default value: ${
-          Constants.DEFAULT_HEARTBEAT_INTERVAL
-        }`
-      )
-    return Constants.DEFAULT_HEARTBEAT_INTERVAL
+  public inAcceptedState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED
   }
 
-  public setSupervisionUrl (url: string): void {
-    if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey)
-    ) {
-      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url)
-    } else {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.stationInfo!.supervisionUrls = url
-      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
-      this.saveStationInfo()
-    }
+  public inPendingState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.PENDING
   }
 
-  public startHeartbeat (): void {
-    if (this.getHeartbeatInterval() > 0 && this.heartbeatSetInterval == null) {
-      this.heartbeatSetInterval = setInterval(() => {
-        this.ocppRequestService
-          .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
-          .catch((error: unknown) => {
-            logger.error(
-              `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
-              error
-            )
-          })
-      }, this.getHeartbeatInterval())
-      logger.info(
-        `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
-          this.getHeartbeatInterval()
-        )}`
-      )
-    } else if (this.heartbeatSetInterval != null) {
-      logger.info(
-        `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
-          this.getHeartbeatInterval()
-        )}`
-      )
-    } else {
-      logger.error(
-        `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`
-      )
-    }
+  public inRejectedState (): boolean {
+    return this.bootNotificationResponse?.status === RegistrationStatusEnumType.REJECTED
   }
 
-  public restartHeartbeat (): void {
-    // Stop heartbeat
-    this.stopHeartbeat()
-    // Start heartbeat
-    this.startHeartbeat()
+  public inUnknownState (): boolean {
+    return this.bootNotificationResponse?.status == null
   }
 
-  public restartWebSocketPing (): void {
-    // Stop WebSocket ping
-    this.stopWebSocketPing()
-    // Start WebSocket ping
-    this.startWebSocketPing()
+  public isChargingStationAvailable (): boolean {
+    return this.getConnectorStatus(0)?.availability === AvailabilityType.Operative
   }
 
-  public startMeterValues (connectorId: number, interval: number): void {
-    if (connectorId === 0) {
-      logger.error(`${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}`)
-      return
-    }
-    const connectorStatus = this.getConnectorStatus(connectorId)
-    if (connectorStatus == null) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on non existing connector id
-          ${connectorId}`
-      )
-      return
-    }
-    if (connectorStatus.transactionStarted === false) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started`
-      )
-      return
-    } else if (
-      connectorStatus.transactionStarted === true &&
-      connectorStatus.transactionId == null
-    ) {
-      logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id`
+  public isConnectorAvailable (connectorId: number): boolean {
+    return (
+      connectorId > 0 &&
+      this.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative
+    )
+  }
+
+  public isConnectorReservable (
+    reservationId: number,
+    idTag?: string,
+    connectorId?: number
+  ): boolean {
+    const reservation = this.getReservationBy('reservationId', reservationId)
+    const reservationExists = reservation != null && !hasReservationExpired(reservation)
+    if (arguments.length === 1) {
+      return !reservationExists
+    } else if (arguments.length > 1) {
+      const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined
+      const userReservationExists =
+        userReservation != null && !hasReservationExpired(userReservation)
+      const notConnectorZero = connectorId == null ? true : connectorId > 0
+      const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0
+      return (
+        !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
       )
-      return
     }
-    if (interval > 0) {
-      connectorStatus.transactionSetInterval = setInterval(() => {
-        const meterValue = buildMeterValue(
-          this,
-          connectorId,
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          connectorStatus.transactionId!,
-          interval
-        )
-        this.ocppRequestService
-          .requestHandler<MeterValuesRequest, MeterValuesResponse>(
-          this,
-          RequestCommand.METER_VALUES,
-          {
-            connectorId,
-            transactionId: connectorStatus.transactionId,
-            meterValue: [meterValue]
-          }
-        )
-          .catch((error: unknown) => {
-            logger.error(
-              `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
-              error
-            )
-          })
-      }, interval)
-    } else {
-      logger.error(
-        `${this.logPrefix()} Charging station ${
-          StandardParametersKey.MeterValueSampleInterval
-        } configuration set to ${interval}, not sending MeterValues`
+    return false
+  }
+
+  public isWebSocketConnectionOpened (): boolean {
+    return this.wsConnection?.readyState === WebSocket.OPEN
+  }
+
+  public logPrefix = (): string => {
+    if (
+      this instanceof ChargingStation &&
+      this.stationInfo != null &&
+      isNotEmptyString(this.stationInfo.chargingStationId)
+    ) {
+      return logPrefix(` ${this.stationInfo.chargingStationId} |`)
+    }
+    let stationTemplate: ChargingStationTemplate | undefined
+    try {
+      stationTemplate = JSON.parse(
+        readFileSync(this.templateFile, 'utf8')
+      ) as ChargingStationTemplate
+    } catch {
+      // Ignore
+    }
+    return logPrefix(` ${getChargingStationId(this.index, stationTemplate)} |`)
+  }
+
+  public openWSConnection (
+    options?: WsOptions,
+    params?: { closeOpened?: boolean; terminateOpened?: boolean }
+  ): void {
+    options = {
+      handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
+      ...this.stationInfo?.wsOptions,
+      ...options,
+    }
+    params = { ...{ closeOpened: false, terminateOpened: false }, ...params }
+    if (!checkChargingStationState(this, this.logPrefix())) {
+      return
+    }
+    if (this.stationInfo?.supervisionUser != null && this.stationInfo.supervisionPassword != null) {
+      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`
+    }
+    if (params.closeOpened) {
+      this.closeWSConnection()
+    }
+    if (params.terminateOpened) {
+      this.terminateWSConnection()
+    }
+
+    if (this.isWebSocketConnectionOpened()) {
+      logger.warn(
+        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.href} is already opened`
       )
+      return
     }
+
+    logger.info(`${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.href}`)
+
+    this.wsConnection = new WebSocket(
+      this.wsConnectionUrl,
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      `ocpp${this.stationInfo?.ocppVersion}`,
+      options
+    )
+
+    // Handle WebSocket message
+    this.wsConnection.on('message', data => {
+      this.onMessage(data).catch(Constants.EMPTY_FUNCTION)
+    })
+    // Handle WebSocket error
+    this.wsConnection.on('error', this.onError.bind(this))
+    // Handle WebSocket close
+    this.wsConnection.on('close', this.onClose.bind(this))
+    // Handle WebSocket open
+    this.wsConnection.on('open', () => {
+      this.onOpen().catch((error: unknown) =>
+        logger.error(`${this.logPrefix()} Error while opening WebSocket connection:`, error)
+      )
+    })
+    // Handle WebSocket ping
+    this.wsConnection.on('ping', this.onPing.bind(this))
+    // Handle WebSocket pong
+    this.wsConnection.on('pong', this.onPong.bind(this))
   }
 
-  public stopMeterValues (connectorId: number): void {
-    const connectorStatus = this.getConnectorStatus(connectorId)
-    if (connectorStatus?.transactionSetInterval != null) {
-      clearInterval(connectorStatus.transactionSetInterval)
+  public async removeReservation (
+    reservation: Reservation,
+    reason: ReservationTerminationReason
+  ): Promise<void> {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const connector = this.getConnectorStatus(reservation.connectorId)!
+    switch (reason) {
+      case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
+      case ReservationTerminationReason.TRANSACTION_STARTED:
+        delete connector.reservation
+        break
+      case ReservationTerminationReason.EXPIRED:
+      case ReservationTerminationReason.REPLACE_EXISTING:
+      case ReservationTerminationReason.RESERVATION_CANCELED:
+        await sendAndSetConnectorStatus(
+          this,
+          reservation.connectorId,
+          ConnectorStatusEnum.Available,
+          undefined,
+          { send: reservation.connectorId !== 0 }
+        )
+        delete connector.reservation
+        break
+      default:
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        throw new BaseError(`Unknown reservation termination reason '${reason}'`)
     }
   }
 
-  private add (): void {
-    this.emit(ChargingStationEvents.added)
+  public async reset (reason?: StopTransactionReason): Promise<void> {
+    await this.stop(reason)
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    await sleep(this.stationInfo!.resetTime!)
+    this.initialize()
+    this.start()
   }
 
-  public async delete (deleteConfiguration = true): Promise<void> {
-    if (this.started) {
-      await this.stop()
+  public restartHeartbeat (): void {
+    // Stop heartbeat
+    this.stopHeartbeat()
+    // Start heartbeat
+    this.startHeartbeat()
+  }
+
+  public restartMeterValues (connectorId: number, interval: number): void {
+    this.stopMeterValues(connectorId)
+    this.startMeterValues(connectorId, interval)
+  }
+
+  public restartWebSocketPing (): void {
+    // Stop WebSocket ping
+    this.stopWebSocketPing()
+    // Start WebSocket ping
+    this.startWebSocketPing()
+  }
+
+  public saveOcppConfiguration (): void {
+    if (this.stationInfo?.ocppPersistentConfiguration === true) {
+      this.saveConfiguration()
+    }
+  }
+
+  public setSupervisionUrl (url: string): void {
+    if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey)
+    ) {
+      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey, url)
+    } else {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.stationInfo!.supervisionUrls = url
+      this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl()
+      this.saveStationInfo()
     }
-    AutomaticTransactionGenerator.deleteInstance(this)
-    PerformanceStatistics.deleteInstance(this.stationInfo?.hashId)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.idTagsCache.deleteIdTags(getIdTagsFile(this.stationInfo!)!)
-    this.requests.clear()
-    this.connectors.clear()
-    this.evses.clear()
-    this.templateFileWatcher?.unref()
-    deleteConfiguration && rmSync(this.configurationFile, { force: true })
-    this.chargingStationWorkerBroadcastChannel.unref()
-    this.emit(ChargingStationEvents.deleted)
-    this.removeAllListeners()
   }
 
   public start (): void {
@@ -751,162 +873,142 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  public async stop (
-    reason?: StopTransactionReason,
-    stopTransactions = this.stationInfo?.stopTransactionsOnStopped
-  ): Promise<void> {
-    if (this.started) {
-      if (!this.stopping) {
-        this.stopping = true
-        await this.stopMessageSequence(reason, stopTransactions)
-        this.closeWSConnection()
-        if (this.stationInfo?.enableStatistics === true) {
-          this.performanceStatistics?.stop()
-        }
-        this.templateFileWatcher?.close()
-        delete this.bootNotificationResponse
-        this.started = false
-        this.saveConfiguration()
-        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
-        this.emit(ChargingStationEvents.stopped)
-        this.stopping = false
-      } else {
-        logger.warn(`${this.logPrefix()} Charging station is already stopping...`)
+  public startAutomaticTransactionGenerator (
+    connectorIds?: number[],
+    stopAbsoluteDuration?: boolean
+  ): void {
+    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this)
+    if (isNotEmptyArray(connectorIds)) {
+      for (const connectorId of connectorIds) {
+        this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration)
       }
     } else {
-      logger.warn(`${this.logPrefix()} Charging station is already stopped...`)
+      this.automaticTransactionGenerator?.start(stopAbsoluteDuration)
     }
+    this.saveAutomaticTransactionGeneratorConfiguration()
+    this.emit(ChargingStationEvents.updated)
   }
 
-  public async reset (reason?: StopTransactionReason): Promise<void> {
-    await this.stop(reason)
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    await sleep(this.stationInfo!.resetTime!)
-    this.initialize()
-    this.start()
-  }
-
-  public saveOcppConfiguration (): void {
-    if (this.stationInfo?.ocppPersistentConfiguration === true) {
-      this.saveConfiguration()
+  public startHeartbeat (): void {
+    const heartbeatInterval = this.getHeartbeatInterval()
+    if (heartbeatInterval > 0 && this.heartbeatSetInterval == null) {
+      this.heartbeatSetInterval = setInterval(() => {
+        this.ocppRequestService
+          .requestHandler<HeartbeatRequest, HeartbeatResponse>(this, RequestCommand.HEARTBEAT)
+          .catch((error: unknown) => {
+            logger.error(
+              `${this.logPrefix()} Error while sending '${RequestCommand.HEARTBEAT}':`,
+              error
+            )
+          })
+      }, heartbeatInterval)
+      logger.info(
+        `${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
+          heartbeatInterval
+        )}`
+      )
+    } else if (this.heartbeatSetInterval != null) {
+      logger.info(
+        `${this.logPrefix()} Heartbeat already started every ${formatDurationMilliSeconds(
+          heartbeatInterval
+        )}`
+      )
+    } else {
+      logger.error(
+        `${this.logPrefix()} Heartbeat interval set to ${heartbeatInterval.toString()}, not starting the heartbeat`
+      )
     }
   }
 
-  public bufferMessage (message: string): void {
-    this.messageBuffer.add(message)
-    this.setIntervalFlushMessageBuffer()
-  }
-
-  public openWSConnection (
-    options?: WsOptions,
-    params?: { closeOpened?: boolean, terminateOpened?: boolean }
-  ): void {
-    options = {
-      handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
-      ...this.stationInfo?.wsOptions,
-      ...options
-    }
-    params = { ...{ closeOpened: false, terminateOpened: false }, ...params }
-    if (!checkChargingStation(this, this.logPrefix())) {
+  public startMeterValues (connectorId: number, interval: number): void {
+    if (connectorId === 0) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`
+      )
       return
     }
-    if (this.stationInfo?.supervisionUser != null && this.stationInfo.supervisionPassword != null) {
-      options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`
-    }
-    if (params.closeOpened === true) {
-      this.closeWSConnection()
-    }
-    if (params.terminateOpened === true) {
-      this.terminateWSConnection()
+    const connectorStatus = this.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on non existing connector id
+          ${connectorId.toString()}`
+      )
+      return
     }
-
-    if (this.isWebSocketConnectionOpened()) {
-      logger.warn(
-        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.href} is already opened`
+    if (connectorStatus.transactionStarted === false) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction started`
+      )
+      return
+    } else if (
+      connectorStatus.transactionStarted === true &&
+      connectorStatus.transactionId == null
+    ) {
+      logger.error(
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()} with no transaction id`
       )
       return
     }
-
-    logger.info(`${this.logPrefix()} Open OCPP connection to URL ${this.wsConnectionUrl.href}`)
-
-    this.wsConnection = new WebSocket(
-      this.wsConnectionUrl,
-      `ocpp${this.stationInfo?.ocppVersion}`,
-      options
-    )
-
-    // Handle WebSocket message
-    this.wsConnection.on('message', data => {
-      this.onMessage(data).catch(Constants.EMPTY_FUNCTION)
-    })
-    // Handle WebSocket error
-    this.wsConnection.on('error', this.onError.bind(this))
-    // Handle WebSocket close
-    this.wsConnection.on('close', this.onClose.bind(this))
-    // Handle WebSocket open
-    this.wsConnection.on('open', () => {
-      this.onOpen().catch((error: unknown) =>
-        logger.error(`${this.logPrefix()} Error while opening WebSocket connection:`, error)
+    if (interval > 0) {
+      connectorStatus.transactionSetInterval = setInterval(() => {
+        const meterValue = buildMeterValue(
+          this,
+          connectorId,
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          connectorStatus.transactionId!,
+          interval
+        )
+        this.ocppRequestService
+          .requestHandler<MeterValuesRequest, MeterValuesResponse>(
+            this,
+            RequestCommand.METER_VALUES,
+            {
+              connectorId,
+              meterValue: [meterValue],
+              transactionId: connectorStatus.transactionId,
+            }
+          )
+          .catch((error: unknown) => {
+            logger.error(
+              `${this.logPrefix()} Error while sending '${RequestCommand.METER_VALUES}':`,
+              error
+            )
+          })
+      }, interval)
+    } else {
+      logger.error(
+        `${this.logPrefix()} Charging station ${
+          StandardParametersKey.MeterValueSampleInterval
+        } configuration set to ${interval.toString()}, not sending MeterValues`
       )
-    })
-    // Handle WebSocket ping
-    this.wsConnection.on('ping', this.onPing.bind(this))
-    // Handle WebSocket pong
-    this.wsConnection.on('pong', this.onPong.bind(this))
-  }
-
-  public closeWSConnection (): void {
-    if (this.isWebSocketConnectionOpened()) {
-      this.wsConnection?.close()
-      this.wsConnection = null
     }
   }
 
-  public getAutomaticTransactionGeneratorConfiguration ():
-  | AutomaticTransactionGeneratorConfiguration
-  | undefined {
-    if (this.automaticTransactionGeneratorConfiguration == null) {
-      let automaticTransactionGeneratorConfiguration:
-      | AutomaticTransactionGeneratorConfiguration
-      | undefined
-      const stationTemplate = this.getTemplateFromFile()
-      const stationConfiguration = this.getConfigurationFromFile()
-      if (
-        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
-        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
-        stationConfiguration?.automaticTransactionGenerator != null
-      ) {
-        automaticTransactionGeneratorConfiguration =
-          stationConfiguration.automaticTransactionGenerator
+  public async stop (
+    reason?: StopTransactionReason,
+    stopTransactions = this.stationInfo?.stopTransactionsOnStopped
+  ): Promise<void> {
+    if (this.started) {
+      if (!this.stopping) {
+        this.stopping = true
+        await this.stopMessageSequence(reason, stopTransactions)
+        this.closeWSConnection()
+        if (this.stationInfo?.enableStatistics === true) {
+          this.performanceStatistics?.stop()
+        }
+        this.templateFileWatcher?.close()
+        delete this.bootNotificationResponse
+        this.started = false
+        this.saveConfiguration()
+        this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
+        this.emit(ChargingStationEvents.stopped)
+        this.stopping = false
       } else {
-        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator
-      }
-      this.automaticTransactionGeneratorConfiguration = {
-        ...Constants.DEFAULT_ATG_CONFIGURATION,
-        ...automaticTransactionGeneratorConfiguration
-      }
-    }
-    return this.automaticTransactionGeneratorConfiguration
-  }
-
-  public getAutomaticTransactionGeneratorStatuses (): Status[] | undefined {
-    return this.getConfigurationFromFile()?.automaticTransactionGeneratorStatuses
-  }
-
-  public startAutomaticTransactionGenerator (
-    connectorIds?: number[],
-    stopAbsoluteDuration?: boolean
-  ): void {
-    this.automaticTransactionGenerator = AutomaticTransactionGenerator.getInstance(this)
-    if (isNotEmptyArray(connectorIds)) {
-      for (const connectorId of connectorIds) {
-        this.automaticTransactionGenerator?.startConnector(connectorId, stopAbsoluteDuration)
+        logger.warn(`${this.logPrefix()} Charging station is already stopping...`)
       }
     } else {
-      this.automaticTransactionGenerator?.start(stopAbsoluteDuration)
+      logger.warn(`${this.logPrefix()} Charging station is already stopped...`)
     }
-    this.saveAutomaticTransactionGeneratorConfiguration()
-    this.emit(ChargingStationEvents.updated)
   }
 
   public stopAutomaticTransactionGenerator (connectorIds?: number[]): void {
@@ -921,6 +1023,13 @@ export class ChargingStation extends EventEmitter {
     this.emit(ChargingStationEvents.updated)
   }
 
+  public stopMeterValues (connectorId: number): void {
+    const connectorStatus = this.getConnectorStatus(connectorId)
+    if (connectorStatus?.transactionSetInterval != null) {
+      clearInterval(connectorStatus.transactionSetInterval)
+    }
+  }
+
   public async stopTransactionOnConnector (
     connectorId: number,
     reason?: StopTransactionReason
@@ -941,133 +1050,194 @@ export class ChargingStation extends EventEmitter {
         RequestCommand.METER_VALUES,
         {
           connectorId,
+          meterValue: [transactionEndMeterValue],
           transactionId,
-          meterValue: [transactionEndMeterValue]
         }
       )
     }
     return await this.ocppRequestService.requestHandler<
-    Partial<StopTransactionRequest>,
-    StopTransactionResponse
+      Partial<StopTransactionRequest>,
+      StopTransactionResponse
     >(this, RequestCommand.STOP_TRANSACTION, {
-      transactionId,
       meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId, true),
-      ...(reason != null && { reason })
+      transactionId,
+      ...(reason != null && { reason }),
     })
   }
 
-  public getReserveConnectorZeroSupported (): boolean {
-    return convertToBoolean(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value
-    )
+  private add (): void {
+    this.emit(ChargingStationEvents.added)
   }
 
-  public async addReservation (reservation: Reservation): Promise<void> {
-    const reservationFound = this.getReservationBy('reservationId', reservation.reservationId)
-    if (reservationFound != null) {
-      await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING)
+  private clearIntervalFlushMessageBuffer (): void {
+    if (this.flushMessageBufferSetInterval != null) {
+      clearInterval(this.flushMessageBufferSetInterval)
+      delete this.flushMessageBufferSetInterval
     }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.getConnectorStatus(reservation.connectorId)!.reservation = reservation
-    await sendAndSetConnectorStatus(
-      this,
-      reservation.connectorId,
-      ConnectorStatusEnum.Reserved,
-      undefined,
-      { send: reservation.connectorId !== 0 }
-    )
   }
 
-  public async removeReservation (
-    reservation: Reservation,
-    reason: ReservationTerminationReason
-  ): Promise<void> {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const connector = this.getConnectorStatus(reservation.connectorId)!
-    switch (reason) {
-      case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
-      case ReservationTerminationReason.TRANSACTION_STARTED:
-        delete connector.reservation
-        break
-      case ReservationTerminationReason.RESERVATION_CANCELED:
-      case ReservationTerminationReason.REPLACE_EXISTING:
-      case ReservationTerminationReason.EXPIRED:
-        await sendAndSetConnectorStatus(
-          this,
-          reservation.connectorId,
-          ConnectorStatusEnum.Available,
-          undefined,
-          { send: reservation.connectorId !== 0 }
-        )
-        delete connector.reservation
-        break
-      default:
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        throw new BaseError(`Unknown reservation termination reason '${reason}'`)
+  private flushMessageBuffer (): void {
+    if (!this.flushingMessageBuffer && this.messageQueue.length > 0) {
+      this.flushingMessageBuffer = true
+      this.sendMessageBuffer(() => {
+        this.flushingMessageBuffer = false
+      })
     }
   }
 
-  public getReservationBy (
-    filterKey: ReservationKey,
-    value: number | string
-  ): Reservation | undefined {
-    if (this.hasEvses) {
-      for (const evseStatus of this.evses.values()) {
-        for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus.reservation?.[filterKey] === value) {
-            return connectorStatus.reservation
-          }
-        }
-      }
-    } else {
-      for (const connectorStatus of this.connectors.values()) {
-        if (connectorStatus.reservation?.[filterKey] === value) {
-          return connectorStatus.reservation
-        }
-      }
+  private getAmperageLimitation (): number | undefined {
+    if (
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) != null
+    ) {
+      return (
+        convertToInt(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey)?.value) /
+        getAmperageLimitationUnitDivider(this.stationInfo)
+      )
     }
   }
 
-  public isConnectorReservable (
-    reservationId: number,
-    idTag?: string,
-    connectorId?: number
-  ): boolean {
-    const reservation = this.getReservationBy('reservationId', reservationId)
-    const reservationExists = reservation != null && !hasReservationExpired(reservation)
-    if (arguments.length === 1) {
-      return !reservationExists
-    } else if (arguments.length > 1) {
-      const userReservation = idTag != null ? this.getReservationBy('idTag', idTag) : undefined
-      const userReservationExists =
-        userReservation != null && !hasReservationExpired(userReservation)
-      const notConnectorZero = connectorId == null ? true : connectorId > 0
-      const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0
-      return (
-        !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
-      )
+  private getCachedRequest (
+    messageType: MessageType | undefined,
+    messageId: string
+  ): CachedRequest | undefined {
+    const cachedRequest = this.requests.get(messageId)
+    if (Array.isArray(cachedRequest)) {
+      return cachedRequest
     }
-    return false
+    throw new OCPPError(
+      ErrorType.PROTOCOL_ERROR,
+      `Cached request for message id '${messageId}' ${getMessageTypeString(
+        messageType
+      )} is not an array`,
+      undefined,
+      cachedRequest
+    )
   }
 
-  private setIntervalFlushMessageBuffer (): void {
-    if (this.flushMessageBufferSetInterval == null) {
-      this.flushMessageBufferSetInterval = setInterval(() => {
-        if (this.isWebSocketConnectionOpened() && this.inAcceptedState()) {
-          this.flushMessageBuffer()
-        }
-        if (this.messageBuffer.size === 0) {
-          this.clearIntervalFlushMessageBuffer()
+  private getConfigurationFromFile (): ChargingStationConfiguration | undefined {
+    let configuration: ChargingStationConfiguration | undefined
+    if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) {
+      try {
+        if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
+          configuration = this.sharedLRUCache.getChargingStationConfiguration(
+            this.configurationFileHash
+          )
+        } else {
+          const measureId = `${FileType.ChargingStationConfiguration} read`
+          const beginId = PerformanceStatistics.beginMeasure(measureId)
+          configuration = JSON.parse(
+            readFileSync(this.configurationFile, 'utf8')
+          ) as ChargingStationConfiguration
+          PerformanceStatistics.endMeasure(measureId, beginId)
+          this.sharedLRUCache.setChargingStationConfiguration(configuration)
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.configurationFileHash = configuration.configurationHash!
         }
-      }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL)
+      } catch (error) {
+        handleFileException(
+          this.configurationFile,
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
+        )
+      }
     }
+    return configuration
   }
 
-  private clearIntervalFlushMessageBuffer (): void {
-    if (this.flushMessageBufferSetInterval != null) {
-      clearInterval(this.flushMessageBufferSetInterval)
-      delete this.flushMessageBufferSetInterval
+  private getConfiguredSupervisionUrl (): URL {
+    let configuredSupervisionUrl: string | undefined
+    const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls()
+    if (isNotEmptyArray(supervisionUrls)) {
+      let configuredSupervisionUrlIndex: number
+      switch (Configuration.getSupervisionUrlDistribution()) {
+        case SupervisionUrlDistribution.RANDOM:
+          configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length)
+          break
+        case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
+        case SupervisionUrlDistribution.ROUND_ROBIN:
+        default:
+          !Object.values(SupervisionUrlDistribution).includes(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            Configuration.getSupervisionUrlDistribution()!
+          ) &&
+            logger.warn(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
+              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' in configuration from values '${SupervisionUrlDistribution.toString()}', defaulting to '${
+                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
+              }'`
+            )
+          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length
+          break
+      }
+      configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex]
+    } else if (typeof supervisionUrls === 'string') {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      configuredSupervisionUrl = supervisionUrls!
+    }
+    if (isNotEmptyString(configuredSupervisionUrl)) {
+      return new URL(configuredSupervisionUrl)
+    }
+    const errorMsg = 'No supervision url(s) configured'
+    logger.error(`${this.logPrefix()} ${errorMsg}`)
+    throw new BaseError(errorMsg)
+  }
+
+  // 0 for disabling
+  private getConnectionTimeout (): number {
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) != null) {
+      return convertToInt(
+        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)?.value ??
+          Constants.DEFAULT_CONNECTION_TIMEOUT
+      )
+    }
+    return Constants.DEFAULT_CONNECTION_TIMEOUT
+  }
+
+  private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType {
+    return (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (stationInfo ?? this.stationInfo!).currentOutType ??
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      Constants.DEFAULT_STATION_INFO.currentOutType!
+    )
+  }
+
+  private getEnergyActiveImportRegister (
+    connectorStatus: ConnectorStatus | undefined,
+    rounded = false
+  ): number {
+    if (this.stationInfo?.meteringPerTransaction === true) {
+      return (
+        (rounded
+          ? connectorStatus?.transactionEnergyActiveImportRegisterValue != null
+            ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue)
+            : undefined
+          : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
+      )
+    }
+    return (
+      (rounded
+        ? connectorStatus?.energyActiveImportRegisterValue != null
+          ? Math.round(connectorStatus.energyActiveImportRegisterValue)
+          : undefined
+        : connectorStatus?.energyActiveImportRegisterValue) ?? 0
+    )
+  }
+
+  private getMaximumAmperage (stationInfo?: ChargingStationInfo): number | undefined {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const maximumPower = (stationInfo ?? this.stationInfo!).maximumPower!
+    switch (this.getCurrentOutType(stationInfo)) {
+      case CurrentType.AC:
+        return ACElectricUtils.amperagePerPhaseFromPower(
+          this.getNumberOfPhases(stationInfo),
+          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
+          this.getVoltageOut(stationInfo)
+        )
+      case CurrentType.DC:
+        return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo))
     }
   }
 
@@ -1093,65 +1263,82 @@ export class ChargingStation extends EventEmitter {
     return 0
   }
 
-  private flushMessageBuffer (): void {
-    if (this.messageBuffer.size > 0) {
-      for (const message of this.messageBuffer.values()) {
-        let beginId: string | undefined
-        let commandName: RequestCommand | undefined
-        const [messageType] = JSON.parse(message) as OutgoingRequest | Response | ErrorResponse
-        const isRequest = messageType === MessageType.CALL_MESSAGE
-        if (isRequest) {
-          [, , commandName] = JSON.parse(message) as OutgoingRequest
-          beginId = PerformanceStatistics.beginMeasure(commandName)
-        }
-        this.wsConnection?.send(message, (error?: Error) => {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!)
-          if (error == null) {
-            logger.debug(
-              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
-                messageType
-              )} OCPP message sent '${JSON.stringify(message)}'`
-            )
-            this.messageBuffer.delete(message)
-          } else {
-            logger.debug(
-              `${this.logPrefix()} >> Buffered ${getMessageTypeString(
-                messageType
-              )} OCPP message '${JSON.stringify(message)}' send failed:`,
-              error
-            )
-          }
-        })
-      }
+  private getOcppConfiguration (
+    ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration
+  ): ChargingStationOcppConfiguration | undefined {
+    let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
+      this.getOcppConfigurationFromFile(ocppPersistentConfiguration)
+    ocppConfiguration ??= this.getOcppConfigurationFromTemplate()
+    return ocppConfiguration
+  }
+
+  private getOcppConfigurationFromFile (
+    ocppPersistentConfiguration?: boolean
+  ): ChargingStationOcppConfiguration | undefined {
+    const configurationKey = this.getConfigurationFromFile()?.configurationKey
+    if (ocppPersistentConfiguration && Array.isArray(configurationKey)) {
+      return { configurationKey }
     }
+    return undefined
   }
 
-  private getTemplateFromFile (): ChargingStationTemplate | undefined {
-    let template: ChargingStationTemplate | undefined
-    try {
-      if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) {
-        template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash)
-      } else {
-        const measureId = `${FileType.ChargingStationTemplate} read`
-        const beginId = PerformanceStatistics.beginMeasure(measureId)
-        template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate
-        PerformanceStatistics.endMeasure(measureId, beginId)
-        template.templateHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
-          .update(JSON.stringify(template))
-          .digest('hex')
-        this.sharedLRUCache.setChargingStationTemplate(template)
-        this.templateFileHash = template.templateHash
+  private getOcppConfigurationFromTemplate (): ChargingStationOcppConfiguration | undefined {
+    return this.getTemplateFromFile()?.Configuration
+  }
+
+  private getPowerDivider (): number {
+    let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()
+    if (this.stationInfo?.powerSharedByConnectors === true) {
+      powerDivider = this.getNumberOfRunningTransactions()
+    }
+    return powerDivider
+  }
+
+  private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo {
+    const stationInfoFromTemplate = this.getStationInfoFromTemplate()
+    options?.persistentConfiguration != null &&
+      (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration)
+    const stationInfoFromFile = this.getStationInfoFromFile(
+      stationInfoFromTemplate.stationInfoPersistentConfiguration
+    )
+    let stationInfo: ChargingStationInfo
+    // Priority:
+    // 1. charging station info from template
+    // 2. charging station info from configuration file
+    if (
+      stationInfoFromFile != null &&
+      stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
+    ) {
+      stationInfo = stationInfoFromFile
+    } else {
+      stationInfo = stationInfoFromTemplate
+      stationInfoFromFile != null &&
+        propagateSerialNumber(this.getTemplateFromFile(), stationInfoFromFile, stationInfo)
+    }
+    return setChargingStationOptions(
+      mergeDeepRight(Constants.DEFAULT_STATION_INFO, stationInfo),
+      options
+    )
+  }
+
+  private getStationInfoFromFile (
+    stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO
+      .stationInfoPersistentConfiguration
+  ): ChargingStationInfo | undefined {
+    let stationInfo: ChargingStationInfo | undefined
+    if (stationInfoPersistentConfiguration) {
+      stationInfo = this.getConfigurationFromFile()?.stationInfo
+      if (stationInfo != null) {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        delete stationInfo.infoHash
+        delete (stationInfo as ChargingStationTemplate).numberOfConnectors
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        stationInfo.templateIndex ??= this.index
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        stationInfo.templateName ??= buildTemplateName(this.templateFile)
       }
-    } catch (error) {
-      handleFileException(
-        this.templateFile,
-        FileType.ChargingStationTemplate,
-        error as NodeJS.ErrnoException,
-        this.logPrefix()
-      )
     }
-    return template
+    return stationInfo
   }
 
   private getStationInfoFromTemplate (): ChargingStationInfo {
@@ -1170,17 +1357,16 @@ export class ChargingStation extends EventEmitter {
     stationInfo.chargingStationId = getChargingStationId(this.index, stationTemplate)
     createSerialNumber(stationTemplate, stationInfo)
     stationInfo.voltageOut = this.getVoltageOut(stationInfo)
-    if (isNotEmptyArray(stationTemplate.power)) {
+    if (isNotEmptyArray<number>(stationTemplate.power)) {
       const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length)
       stationInfo.maximumPower =
         stationTemplate.powerUnit === PowerUnits.KILO_WATT
           ? stationTemplate.power[powerArrayRandomIndex] * 1000
           : stationTemplate.power[powerArrayRandomIndex]
-    } else {
+    } else if (typeof stationTemplate.power === 'number') {
       stationInfo.maximumPower =
         stationTemplate.powerUnit === PowerUnits.KILO_WATT
-          ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          stationTemplate.power! * 1000
+          ? stationTemplate.power * 1000
           : stationTemplate.power
     }
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo)
@@ -1201,63 +1387,130 @@ export class ChargingStation extends EventEmitter {
     return stationInfo
   }
 
-  private getStationInfoFromFile (
-    stationInfoPersistentConfiguration: boolean | undefined = Constants.DEFAULT_STATION_INFO
-      .stationInfoPersistentConfiguration
-  ): ChargingStationInfo | undefined {
-    let stationInfo: ChargingStationInfo | undefined
-    if (stationInfoPersistentConfiguration === true) {
-      stationInfo = this.getConfigurationFromFile()?.stationInfo
-      if (stationInfo != null) {
-        delete stationInfo.infoHash
-        delete (stationInfo as ChargingStationTemplate).numberOfConnectors
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        if (stationInfo.templateIndex == null) {
-          stationInfo.templateIndex = this.index
-        }
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        if (stationInfo.templateName == null) {
-          stationInfo.templateName = buildTemplateName(this.templateFile)
-        }
-      }
-    }
-    return stationInfo
-  }
-
-  private getStationInfo (options?: ChargingStationOptions): ChargingStationInfo {
-    const stationInfoFromTemplate = this.getStationInfoFromTemplate()
-    options?.persistentConfiguration != null &&
-      (stationInfoFromTemplate.stationInfoPersistentConfiguration = options.persistentConfiguration)
-    const stationInfoFromFile = this.getStationInfoFromFile(
-      stationInfoFromTemplate.stationInfoPersistentConfiguration
+  private getTemplateFromFile (): ChargingStationTemplate | undefined {
+    let template: ChargingStationTemplate | undefined
+    try {
+      if (this.sharedLRUCache.hasChargingStationTemplate(this.templateFileHash)) {
+        template = this.sharedLRUCache.getChargingStationTemplate(this.templateFileHash)
+      } else {
+        const measureId = `${FileType.ChargingStationTemplate} read`
+        const beginId = PerformanceStatistics.beginMeasure(measureId)
+        template = JSON.parse(readFileSync(this.templateFile, 'utf8')) as ChargingStationTemplate
+        PerformanceStatistics.endMeasure(measureId, beginId)
+        template.templateHash = hash(
+          Constants.DEFAULT_HASH_ALGORITHM,
+          JSON.stringify(template),
+          'hex'
+        )
+        this.sharedLRUCache.setChargingStationTemplate(template)
+        this.templateFileHash = template.templateHash
+      }
+    } catch (error) {
+      handleFileException(
+        this.templateFile,
+        FileType.ChargingStationTemplate,
+        error as NodeJS.ErrnoException,
+        this.logPrefix()
+      )
+    }
+    return template
+  }
+
+  private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0!
+  }
+
+  private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {
+    return (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (stationInfo ?? this.stationInfo!).voltageOut ??
+      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
     )
-    let stationInfo: ChargingStationInfo
-    // Priority:
-    // 1. charging station info from template
-    // 2. charging station info from configuration file
-    if (
-      stationInfoFromFile != null &&
-      stationInfoFromFile.templateHash === stationInfoFromTemplate.templateHash
-    ) {
-      stationInfo = stationInfoFromFile
-    } else {
-      stationInfo = stationInfoFromTemplate
-      stationInfoFromFile != null &&
-        propagateSerialNumber(this.getTemplateFromFile(), stationInfoFromFile, stationInfo)
+  }
+
+  private getWebSocketPingInterval (): number {
+    return getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null
+      ? convertToInt(getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value)
+      : 0
+  }
+
+  private handleErrorMessage (errorResponse: ErrorResponse): void {
+    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse
+    if (!this.requests.has(messageId)) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Error response for unknown message id '${messageId}'`,
+        undefined,
+        { errorDetails, errorMessage, errorType }
+      )
     }
-    return setChargingStationOptions(
-      mergeDeepRight(Constants.DEFAULT_STATION_INFO, stationInfo),
-      options
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
+    logger.debug(
+      `${this.logPrefix()} << Command '${requestCommandName}' received error response payload: ${JSON.stringify(
+        errorResponse
+      )}`
     )
+    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails))
   }
 
-  private saveStationInfo (): void {
-    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
-      this.saveConfiguration()
+  private async handleIncomingMessage (request: IncomingRequest): Promise<void> {
+    const [messageType, messageId, commandName, commandPayload] = request
+    if (this.requests.has(messageId)) {
+      throw new OCPPError(
+        ErrorType.SECURITY_ERROR,
+        `Received message with duplicate message id '${messageId}'`,
+        commandName,
+        commandPayload
+      )
+    }
+    if (this.stationInfo?.enableStatistics === true) {
+      this.performanceStatistics?.addRequestStatistic(commandName, messageType)
+    }
+    logger.debug(
+      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
+        request
+      )}`
+    )
+    // Process the message
+    await this.ocppIncomingRequestService.incomingRequestHandler(
+      this,
+      messageId,
+      commandName,
+      commandPayload
+    )
+    this.emit(ChargingStationEvents.updated)
+  }
+
+  private handleResponseMessage (response: Response): void {
+    const [messageType, messageId, commandPayload] = response
+    if (!this.requests.has(messageId)) {
+      // Error
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Response for unknown message id '${messageId}'`,
+        undefined,
+        commandPayload
+      )
     }
+    // Respond
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
+      messageType,
+      messageId
+    )!
+    logger.debug(
+      `${this.logPrefix()} << Command '${requestCommandName}' received response payload: ${JSON.stringify(
+        response
+      )}`
+    )
+    responseCallback(commandPayload, requestPayload)
   }
 
   private handleUnsupportedVersion (version: OCPPVersion | undefined): void {
+    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
     const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`
     logger.error(`${this.logPrefix()} ${errorMsg}`)
     throw new BaseError(errorMsg)
@@ -1282,6 +1535,7 @@ export class ChargingStation extends EventEmitter {
       this.initializeConnectorsOrEvsesFromTemplate(stationTemplate)
     }
     this.stationInfo = this.getStationInfo(options)
+    validateStationInfo(this)
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
       isNotEmptyString(this.stationInfo.firmwareVersionPattern) &&
@@ -1328,141 +1582,86 @@ export class ChargingStation extends EventEmitter {
       this.bootNotificationResponse = {
         currentTime: new Date(),
         interval: millisecondsToSeconds(this.getHeartbeatInterval()),
-        status: RegistrationStatusEnumType.ACCEPTED
+        status: RegistrationStatusEnumType.ACCEPTED,
       }
     }
   }
 
-  private initializeOcppServices (): void {
-    const ocppVersion = this.stationInfo?.ocppVersion
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        this.ocppIncomingRequestService =
-          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
-        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
-          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
-        )
-        break
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        this.ocppIncomingRequestService =
-          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
-        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
-          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
-        )
-        break
-      default:
-        this.handleUnsupportedVersion(ocppVersion)
-        break
-    }
-  }
-
-  private initializeOcppConfiguration (): void {
-    if (getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) == null) {
-      addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0')
-    }
-    if (getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) == null) {
-      addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false })
-    }
-    if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) == null
-    ) {
-      addConfigurationKey(
-        this,
-        this.stationInfo.supervisionUrlOcppKey,
-        this.configuredSupervisionUrl.href,
-        { reboot: true }
-      )
-    } else if (
-      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
-      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) != null
-    ) {
-      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey, { save: false })
-    }
-    if (
-      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) == null
-    ) {
-      addConfigurationKey(
-        this,
-        this.stationInfo.amperageLimitationOcppKey,
-        // prettier-ignore
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        (this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)).toString()
-      )
+  private initializeConnectorsFromTemplate (stationTemplate: ChargingStationTemplate): void {
+    if (stationTemplate.Connectors == null && isEmpty(this.connectors)) {
+      const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`
+      logger.error(`${this.logPrefix()} ${errorMsg}`)
+      throw new BaseError(errorMsg)
     }
-    if (getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles) == null) {
-      addConfigurationKey(
-        this,
-        StandardParametersKey.SupportedFeatureProfiles,
-        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
+    if (stationTemplate.Connectors?.[0] == null) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no connector id 0 configuration`
       )
     }
-    addConfigurationKey(
-      this,
-      StandardParametersKey.NumberOfConnectors,
-      this.getNumberOfConnectors().toString(),
-      { readonly: true },
-      { overwrite: true }
-    )
-    if (getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData) == null) {
-      addConfigurationKey(
-        this,
-        StandardParametersKey.MeterValuesSampledData,
-        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+    if (stationTemplate.Connectors != null) {
+      const { configuredMaxConnectors, templateMaxAvailableConnectors, templateMaxConnectors } =
+        checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
+      const connectorsConfigHash = hash(
+        Constants.DEFAULT_HASH_ALGORITHM,
+        `${JSON.stringify(stationTemplate.Connectors)}${configuredMaxConnectors.toString()}`,
+        'hex'
       )
-    }
-    if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) {
-      const connectorsPhaseRotation: string[] = []
-      if (this.hasEvses) {
-        for (const evseStatus of this.evses.values()) {
-          for (const connectorId of evseStatus.connectors.keys()) {
-            connectorsPhaseRotation.push(
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
+      const connectorsConfigChanged =
+        this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash
+      if (isEmpty(this.connectors) || connectorsConfigChanged) {
+        connectorsConfigChanged && this.connectors.clear()
+        this.connectorsConfigurationHash = connectorsConfigHash
+        if (templateMaxConnectors > 0) {
+          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
+            if (
+              connectorId === 0 &&
+              // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+              (stationTemplate.Connectors[connectorId] == null ||
+                !this.getUseConnectorId0(stationTemplate))
+            ) {
+              continue
+            }
+            const templateConnectorId =
+              connectorId > 0 && stationTemplate.randomConnectors === true
+                ? randomInt(1, templateMaxAvailableConnectors)
+                : connectorId
+            const connectorStatus = stationTemplate.Connectors[templateConnectorId]
+            checkStationInfoConnectorStatus(
+              templateConnectorId,
+              connectorStatus,
+              this.logPrefix(),
+              this.templateFile
             )
+            this.connectors.set(connectorId, clone<ConnectorStatus>(connectorStatus))
           }
-        }
-      } else {
-        for (const connectorId of this.connectors.keys()) {
-          connectorsPhaseRotation.push(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
+          initializeConnectorsMapStatus(this.connectors, this.logPrefix())
+          this.saveConnectorsStatus()
+        } else {
+          logger.warn(
+            `${this.logPrefix()} Charging station information from template ${
+              this.templateFile
+            } with no connectors configuration defined, cannot create connectors`
           )
         }
       }
-      addConfigurationKey(
-        this,
-        StandardParametersKey.ConnectorPhaseRotation,
-        connectorsPhaseRotation.toString()
-      )
-    }
-    if (getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests) == null) {
-      addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true')
-    }
-    if (
-      getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) == null &&
-      hasFeatureProfile(this, SupportedFeatureProfiles.LocalAuthListManagement) === true
-    ) {
-      addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false')
-    }
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) == null) {
-      addConfigurationKey(
-        this,
-        StandardParametersKey.ConnectionTimeOut,
-        Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
+    } else {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with no connectors configuration defined, using already defined connectors`
       )
     }
-    this.saveOcppConfiguration()
   }
 
   private initializeConnectorsOrEvsesFromFile (configuration: ChargingStationConfiguration): void {
     if (configuration.connectorsStatus != null && configuration.evsesStatus == null) {
       for (const [connectorId, connectorStatus] of configuration.connectorsStatus.entries()) {
-        this.connectors.set(connectorId, clone<ConnectorStatus>(connectorStatus))
+        this.connectors.set(
+          connectorId,
+          prepareConnectorStatus(clone<ConnectorStatus>(connectorStatus))
+        )
       }
     } else if (configuration.evsesStatus != null && configuration.connectorsStatus == null) {
       for (const [evseId, evseStatusConfiguration] of configuration.evsesStatus.entries()) {
@@ -1474,9 +1673,9 @@ export class ChargingStation extends EventEmitter {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             evseStatusConfiguration.connectorsStatus!.map((connectorStatus, connectorId) => [
               connectorId,
-              connectorStatus
+              prepareConnectorStatus(connectorStatus),
             ])
-          )
+          ),
         })
       }
     } else if (configuration.evsesStatus != null && configuration.connectorsStatus != null) {
@@ -1506,76 +1705,8 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private initializeConnectorsFromTemplate (stationTemplate: ChargingStationTemplate): void {
-    if (stationTemplate.Connectors == null && this.connectors.size === 0) {
-      const errorMsg = `No already defined connectors and charging station information from template ${this.templateFile} with no connectors configuration defined`
-      logger.error(`${this.logPrefix()} ${errorMsg}`)
-      throw new BaseError(errorMsg)
-    }
-    if (stationTemplate.Connectors?.[0] == null) {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no connector id 0 configuration`
-      )
-    }
-    if (stationTemplate.Connectors != null) {
-      const { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors } =
-        checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile)
-      const connectorsConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(
-          `${JSON.stringify(stationTemplate.Connectors)}${configuredMaxConnectors.toString()}`
-        )
-        .digest('hex')
-      const connectorsConfigChanged =
-        this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash
-      if (this.connectors.size === 0 || connectorsConfigChanged) {
-        connectorsConfigChanged && this.connectors.clear()
-        this.connectorsConfigurationHash = connectorsConfigHash
-        if (templateMaxConnectors > 0) {
-          for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
-            if (
-              connectorId === 0 &&
-              // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-              (stationTemplate.Connectors[connectorId] == null ||
-                !this.getUseConnectorId0(stationTemplate))
-            ) {
-              continue
-            }
-            const templateConnectorId =
-              connectorId > 0 && stationTemplate.randomConnectors === true
-                ? randomInt(1, templateMaxAvailableConnectors)
-                : connectorId
-            const connectorStatus = stationTemplate.Connectors[templateConnectorId]
-            checkStationInfoConnectorStatus(
-              templateConnectorId,
-              connectorStatus,
-              this.logPrefix(),
-              this.templateFile
-            )
-            this.connectors.set(connectorId, clone<ConnectorStatus>(connectorStatus))
-          }
-          initializeConnectorsMapStatus(this.connectors, this.logPrefix())
-          this.saveConnectorsStatus()
-        } else {
-          logger.warn(
-            `${this.logPrefix()} Charging station information from template ${
-              this.templateFile
-            } with no connectors configuration defined, cannot create connectors`
-          )
-        }
-      }
-    } else {
-      logger.warn(
-        `${this.logPrefix()} Charging station information from template ${
-          this.templateFile
-        } with no connectors configuration defined, using already defined connectors`
-      )
-    }
-  }
-
   private initializeEvsesFromTemplate (stationTemplate: ChargingStationTemplate): void {
-    if (stationTemplate.Evses == null && this.evses.size === 0) {
+    if (stationTemplate.Evses == null && isEmpty(this.evses)) {
       const errorMsg = `No already defined evses and charging station information from template ${this.templateFile} with no evses configuration defined`
       logger.error(`${this.logPrefix()} ${errorMsg}`)
       throw new BaseError(errorMsg)
@@ -1602,12 +1733,14 @@ export class ChargingStation extends EventEmitter {
       )
     }
     if (stationTemplate.Evses != null) {
-      const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
-        .update(JSON.stringify(stationTemplate.Evses))
-        .digest('hex')
+      const evsesConfigHash = hash(
+        Constants.DEFAULT_HASH_ALGORITHM,
+        JSON.stringify(stationTemplate.Evses),
+        'hex'
+      )
       const evsesConfigChanged =
         this.evses.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash
-      if (this.evses.size === 0 || evsesConfigChanged) {
+      if (isEmpty(this.evses) || evsesConfigChanged) {
         evsesConfigChanged && this.evses.clear()
         this.evsesConfigurationHash = evsesConfigHash
         const templateMaxEvses = getMaxNumberOfEvses(stationTemplate.Evses)
@@ -1615,12 +1748,12 @@ export class ChargingStation extends EventEmitter {
           for (const evseKey in stationTemplate.Evses) {
             const evseId = convertToInt(evseKey)
             this.evses.set(evseId, {
+              availability: AvailabilityType.Operative,
               connectors: buildConnectorsMap(
                 stationTemplate.Evses[evseKey].Connectors,
                 this.logPrefix(),
                 this.templateFile
               ),
-              availability: AvailabilityType.Operative
             })
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             initializeConnectorsMapStatus(this.evses.get(evseId)!.connectors, this.logPrefix())
@@ -1643,236 +1776,145 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private getConfigurationFromFile (): ChargingStationConfiguration | undefined {
-    let configuration: ChargingStationConfiguration | undefined
-    if (isNotEmptyString(this.configurationFile) && existsSync(this.configurationFile)) {
-      try {
-        if (this.sharedLRUCache.hasChargingStationConfiguration(this.configurationFileHash)) {
-          configuration = this.sharedLRUCache.getChargingStationConfiguration(
-            this.configurationFileHash
-          )
-        } else {
-          const measureId = `${FileType.ChargingStationConfiguration} read`
-          const beginId = PerformanceStatistics.beginMeasure(measureId)
-          configuration = JSON.parse(
-            readFileSync(this.configurationFile, 'utf8')
-          ) as ChargingStationConfiguration
-          PerformanceStatistics.endMeasure(measureId, beginId)
-          this.sharedLRUCache.setChargingStationConfiguration(configuration)
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.configurationFileHash = configuration.configurationHash!
-        }
-      } catch (error) {
-        handleFileException(
-          this.configurationFile,
-          FileType.ChargingStationConfiguration,
-          error as NodeJS.ErrnoException,
-          this.logPrefix()
-        )
-      }
+  private initializeOcppConfiguration (): void {
+    if (getConfigurationKey(this, StandardParametersKey.HeartbeatInterval) == null) {
+      addConfigurationKey(this, StandardParametersKey.HeartbeatInterval, '0')
     }
-    return configuration
-  }
-
-  private saveAutomaticTransactionGeneratorConfiguration (): void {
-    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
-      this.saveConfiguration()
+    if (getConfigurationKey(this, StandardParametersKey.HeartBeatInterval) == null) {
+      addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', {
+        visible: false,
+      })
     }
-  }
-
-  private saveConnectorsStatus (): void {
-    this.saveConfiguration()
-  }
-
-  private saveEvsesStatus (): void {
-    this.saveConfiguration()
-  }
-
-  private saveConfiguration (): void {
-    if (isNotEmptyString(this.configurationFile)) {
-      try {
-        if (!existsSync(dirname(this.configurationFile))) {
-          mkdirSync(dirname(this.configurationFile), { recursive: true })
-        }
-        const configurationFromFile = this.getConfigurationFromFile()
-        let configurationData: ChargingStationConfiguration =
-          configurationFromFile != null
-            ? clone<ChargingStationConfiguration>(configurationFromFile)
-            : {}
-        if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
-          configurationData.stationInfo = this.stationInfo
-        } else {
-          delete configurationData.stationInfo
-        }
-        if (
-          this.stationInfo?.ocppPersistentConfiguration === true &&
-          Array.isArray(this.ocppConfiguration?.configurationKey)
-        ) {
-          configurationData.configurationKey = this.ocppConfiguration.configurationKey
-        } else {
-          delete configurationData.configurationKey
-        }
-        configurationData = mergeDeepRight(
-          configurationData,
-          buildChargingStationAutomaticTransactionGeneratorConfiguration(this)
-        )
-        if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) {
-          delete configurationData.automaticTransactionGenerator
-        }
-        if (this.connectors.size > 0) {
-          configurationData.connectorsStatus = buildConnectorsStatus(this)
-        } else {
-          delete configurationData.connectorsStatus
-        }
-        if (this.evses.size > 0) {
-          configurationData.evsesStatus = buildEvsesStatus(this)
-        } else {
-          delete configurationData.evsesStatus
-        }
-        delete configurationData.configurationHash
-        const configurationHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
-          .update(
-            JSON.stringify({
-              stationInfo: configurationData.stationInfo,
-              configurationKey: configurationData.configurationKey,
-              automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
-              ...(this.connectors.size > 0 && {
-                connectorsStatus: configurationData.connectorsStatus
-              }),
-              ...(this.evses.size > 0 && { evsesStatus: configurationData.evsesStatus })
-            } satisfies ChargingStationConfiguration)
-          )
-          .digest('hex')
-        if (this.configurationFileHash !== configurationHash) {
-          AsyncLock.runExclusive(AsyncLockType.configuration, () => {
-            configurationData.configurationHash = configurationHash
-            const measureId = `${FileType.ChargingStationConfiguration} write`
-            const beginId = PerformanceStatistics.beginMeasure(measureId)
-            writeFileSync(
-              this.configurationFile,
-              JSON.stringify(configurationData, undefined, 2),
-              'utf8'
-            )
-            PerformanceStatistics.endMeasure(measureId, beginId)
-            this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
-            this.sharedLRUCache.setChargingStationConfiguration(configurationData)
-            this.configurationFileHash = configurationHash
-          }).catch((error: unknown) => {
-            handleFileException(
-              this.configurationFile,
-              FileType.ChargingStationConfiguration,
-              error as NodeJS.ErrnoException,
-              this.logPrefix()
-            )
-          })
-        } else {
-          logger.debug(
-            `${this.logPrefix()} Not saving unchanged charging station configuration file ${
-              this.configurationFile
-            }`
-          )
-        }
-      } catch (error) {
-        handleFileException(
-          this.configurationFile,
-          FileType.ChargingStationConfiguration,
-          error as NodeJS.ErrnoException,
-          this.logPrefix()
-        )
-      }
-    } else {
-      logger.error(
-        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
+    if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === true &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) == null
+    ) {
+      addConfigurationKey(
+        this,
+        this.stationInfo.supervisionUrlOcppKey,
+        this.configuredSupervisionUrl.href,
+        { reboot: true }
       )
+    } else if (
+      this.stationInfo?.supervisionUrlOcppConfiguration === false &&
+      isNotEmptyString(this.stationInfo.supervisionUrlOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey) != null
+    ) {
+      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey, {
+        save: false,
+      })
     }
-  }
-
-  private getOcppConfigurationFromTemplate (): ChargingStationOcppConfiguration | undefined {
-    return this.getTemplateFromFile()?.Configuration
-  }
-
-  private getOcppConfigurationFromFile (
-    ocppPersistentConfiguration?: boolean
-  ): ChargingStationOcppConfiguration | undefined {
-    const configurationKey = this.getConfigurationFromFile()?.configurationKey
-    if (ocppPersistentConfiguration === true && Array.isArray(configurationKey)) {
-      return { configurationKey }
+    if (
+      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) == null
+    ) {
+      addConfigurationKey(
+        this,
+        this.stationInfo.amperageLimitationOcppKey,
+        // prettier-ignore
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        (this.stationInfo.maximumAmperage! * getAmperageLimitationUnitDivider(this.stationInfo)).toString()
+      )
     }
-    return undefined
-  }
-
-  private getOcppConfiguration (
-    ocppPersistentConfiguration: boolean | undefined = this.stationInfo?.ocppPersistentConfiguration
-  ): ChargingStationOcppConfiguration | undefined {
-    let ocppConfiguration: ChargingStationOcppConfiguration | undefined =
-      this.getOcppConfigurationFromFile(ocppPersistentConfiguration)
-    if (ocppConfiguration == null) {
-      ocppConfiguration = this.getOcppConfigurationFromTemplate()
+    if (getConfigurationKey(this, StandardParametersKey.SupportedFeatureProfiles) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.SupportedFeatureProfiles,
+        `${SupportedFeatureProfiles.Core},${SupportedFeatureProfiles.FirmwareManagement},${SupportedFeatureProfiles.LocalAuthListManagement},${SupportedFeatureProfiles.SmartCharging},${SupportedFeatureProfiles.RemoteTrigger}`
+      )
     }
-    return ocppConfiguration
-  }
-
-  private async onOpen (): Promise<void> {
-    if (this.isWebSocketConnectionOpened()) {
-      this.emit(ChargingStationEvents.updated)
-      logger.info(
-        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} succeeded`
+    addConfigurationKey(
+      this,
+      StandardParametersKey.NumberOfConnectors,
+      this.getNumberOfConnectors().toString(),
+      { readonly: true },
+      { overwrite: true }
+    )
+    if (getConfigurationKey(this, StandardParametersKey.MeterValuesSampledData) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.MeterValuesSampledData,
+        MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       )
-      let registrationRetryCount = 0
-      if (!this.isRegistered()) {
-        // Send BootNotification
-        do {
-          this.bootNotificationResponse = await this.ocppRequestService.requestHandler<
-          BootNotificationRequest,
-          BootNotificationResponse
-          >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
-            skipBufferingOnError: true
-          })
-          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-          if (this.bootNotificationResponse?.currentTime != null) {
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.bootNotificationResponse.currentTime = convertToDate(
-              this.bootNotificationResponse.currentTime
-            )!
-          }
-          if (!this.isRegistered()) {
-            this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount
-            await sleep(
-              // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-              this.bootNotificationResponse?.interval != null
-                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
-                : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL
+    }
+    if (getConfigurationKey(this, StandardParametersKey.ConnectorPhaseRotation) == null) {
+      const connectorsPhaseRotation: string[] = []
+      if (this.hasEvses) {
+        for (const evseStatus of this.evses.values()) {
+          for (const connectorId of evseStatus.connectors.keys()) {
+            connectorsPhaseRotation.push(
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
             )
           }
-        } while (
-          !this.isRegistered() &&
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          (registrationRetryCount <= this.stationInfo!.registrationMaxRetries! ||
-            this.stationInfo?.registrationMaxRetries === -1)
-        )
-      }
-      if (this.isRegistered()) {
-        this.emit(ChargingStationEvents.registered)
-        if (this.inAcceptedState()) {
-          this.emit(ChargingStationEvents.accepted)
         }
       } else {
-        if (this.inRejectedState()) {
-          this.emit(ChargingStationEvents.rejected)
+        for (const connectorId of this.connectors.keys()) {
+          connectorsPhaseRotation.push(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            getPhaseRotationValue(connectorId, this.getNumberOfPhases())!
+          )
         }
-        logger.error(
-          `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount}) or retry disabled (${
-            this.stationInfo?.registrationMaxRetries
-          })`
-        )
       }
-      this.wsConnectionRetryCount = 0
-      this.emit(ChargingStationEvents.updated)
-    } else {
-      logger.warn(
-        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} failed`
+      addConfigurationKey(
+        this,
+        StandardParametersKey.ConnectorPhaseRotation,
+        connectorsPhaseRotation.toString()
       )
     }
+    if (getConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests) == null) {
+      addConfigurationKey(this, StandardParametersKey.AuthorizeRemoteTxRequests, 'true')
+    }
+    if (
+      getConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled) == null &&
+      hasFeatureProfile(this, SupportedFeatureProfiles.LocalAuthListManagement) === true
+    ) {
+      addConfigurationKey(this, StandardParametersKey.LocalAuthListEnabled, 'false')
+    }
+    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) == null) {
+      addConfigurationKey(
+        this,
+        StandardParametersKey.ConnectionTimeOut,
+        Constants.DEFAULT_CONNECTION_TIMEOUT.toString()
+      )
+    }
+    this.saveOcppConfiguration()
+  }
+
+  private initializeOcppServices (): void {
+    const ocppVersion = this.stationInfo?.ocppVersion
+    switch (ocppVersion) {
+      case OCPPVersion.VERSION_16:
+        this.ocppIncomingRequestService =
+          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
+        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
+          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+        )
+        break
+      case OCPPVersion.VERSION_20:
+      case OCPPVersion.VERSION_201:
+        this.ocppIncomingRequestService =
+          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
+        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
+          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+        )
+        break
+      default:
+        this.handleUnsupportedVersion(ocppVersion)
+        break
+    }
+  }
+
+  private internalStopMessageSequence (): void {
+    // Stop WebSocket ping
+    this.stopWebSocketPing()
+    // Stop heartbeat
+    this.stopHeartbeat()
+    // Stop the ATG
+    if (this.automaticTransactionGenerator?.started === true) {
+      this.stopAutomaticTransactionGenerator()
+    }
   }
 
   private onClose (code: WebSocketCloseEventStatusCode, reason: Buffer): void {
@@ -1880,8 +1922,8 @@ export class ChargingStation extends EventEmitter {
     this.emit(ChargingStationEvents.updated)
     switch (code) {
       // Normal close
-      case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
       case WebSocketCloseEventStatusCode.CLOSE_NO_STATUS:
+      case WebSocketCloseEventStatusCode.CLOSE_NORMAL:
         logger.info(
           `${this.logPrefix()} WebSocket normally closed with status '${getWebSocketCloseEventStatusString(
             code
@@ -1900,6 +1942,7 @@ export class ChargingStation extends EventEmitter {
           this.reconnect()
             .then(() => {
               this.emit(ChargingStationEvents.updated)
+              return undefined
             })
             .catch((error: unknown) =>
               logger.error(`${this.logPrefix()} Error while reconnecting:`, error)
@@ -1908,101 +1951,26 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private getCachedRequest (
-    messageType: MessageType | undefined,
-    messageId: string
-  ): CachedRequest | undefined {
-    const cachedRequest = this.requests.get(messageId)
-    if (Array.isArray(cachedRequest)) {
-      return cachedRequest
-    }
-    throw new OCPPError(
-      ErrorType.PROTOCOL_ERROR,
-      `Cached request for message id ${messageId} ${getMessageTypeString(
-        messageType
-      )} is not an array`,
-      undefined,
-      cachedRequest
-    )
-  }
-
-  private async handleIncomingMessage (request: IncomingRequest): Promise<void> {
-    const [messageType, messageId, commandName, commandPayload] = request
-    if (this.stationInfo?.enableStatistics === true) {
-      this.performanceStatistics?.addRequestStatistic(commandName, messageType)
-    }
-    logger.debug(
-      `${this.logPrefix()} << Command '${commandName}' received request payload: ${JSON.stringify(
-        request
-      )}`
-    )
-    // Process the message
-    await this.ocppIncomingRequestService.incomingRequestHandler(
-      this,
-      messageId,
-      commandName,
-      commandPayload
-    )
-    this.emit(ChargingStationEvents.updated)
-  }
-
-  private handleResponseMessage (response: Response): void {
-    const [messageType, messageId, commandPayload] = response
-    if (!this.requests.has(messageId)) {
-      // Error
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        `Response for unknown message id ${messageId}`,
-        undefined,
-        commandPayload
-      )
-    }
-    // Respond
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const [responseCallback, , requestCommandName, requestPayload] = this.getCachedRequest(
-      messageType,
-      messageId
-    )!
-    logger.debug(
-      `${this.logPrefix()} << Command '${requestCommandName}' received response payload: ${JSON.stringify(
-        response
-      )}`
-    )
-    responseCallback(commandPayload, requestPayload)
-  }
-
-  private handleErrorMessage (errorResponse: ErrorResponse): void {
-    const [messageType, messageId, errorType, errorMessage, errorDetails] = errorResponse
-    if (!this.requests.has(messageId)) {
-      // Error
-      throw new OCPPError(
-        ErrorType.INTERNAL_ERROR,
-        `Error response for unknown message id ${messageId}`,
-        undefined,
-        { errorType, errorMessage, errorDetails }
-      )
-    }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
-    logger.debug(
-      `${this.logPrefix()} << Command '${requestCommandName}' received error response payload: ${JSON.stringify(
-        errorResponse
-      )}`
-    )
-    errorCallback(new OCPPError(errorType, errorMessage, requestCommandName, errorDetails))
+  private onError (error: WSError): void {
+    this.closeWSConnection()
+    logger.error(`${this.logPrefix()} WebSocket error:`, error)
   }
 
   private async onMessage (data: RawData): Promise<void> {
-    let request: IncomingRequest | Response | ErrorResponse | undefined
+    let request: ErrorResponse | IncomingRequest | Response | undefined
     let messageType: MessageType | undefined
     let errorMsg: string
     try {
       // eslint-disable-next-line @typescript-eslint/no-base-to-string
-      request = JSON.parse(data.toString()) as IncomingRequest | Response | ErrorResponse
+      request = JSON.parse(data.toString()) as ErrorResponse | IncomingRequest | Response
       if (Array.isArray(request)) {
-        [messageType] = request
+        ;[messageType] = request
         // Check the type of message
         switch (messageType) {
+          // Error Message
+          case MessageType.CALL_ERROR_MESSAGE:
+            this.handleErrorMessage(request as ErrorResponse)
+            break
           // Incoming Message
           case MessageType.CALL_MESSAGE:
             await this.handleIncomingMessage(request as IncomingRequest)
@@ -2011,10 +1979,6 @@ export class ChargingStation extends EventEmitter {
           case MessageType.CALL_RESULT_MESSAGE:
             this.handleResponseMessage(request as Response)
             break
-          // Error Message
-          case MessageType.CALL_ERROR_MESSAGE:
-            this.handleErrorMessage(request as ErrorResponse)
-            break
           // Unknown Message
           default:
             // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -2028,30 +1992,26 @@ export class ChargingStation extends EventEmitter {
           'Incoming message is not an array',
           undefined,
           {
-            request
+            request,
           }
         )
       }
     } catch (error) {
       if (!Array.isArray(request)) {
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, error)
         return
       }
       let commandName: IncomingRequestCommand | undefined
-      let requestCommandName: RequestCommand | IncomingRequestCommand | undefined
+      let requestCommandName: IncomingRequestCommand | RequestCommand | undefined
       let errorCallback: ErrorCallback
       const [, messageId] = request
       switch (messageType) {
-        case MessageType.CALL_MESSAGE:
-          [, , commandName] = request as IncomingRequest
-          // Send error
-          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName)
-          break
-        case MessageType.CALL_RESULT_MESSAGE:
         case MessageType.CALL_ERROR_MESSAGE:
+        case MessageType.CALL_RESULT_MESSAGE:
           if (this.requests.has(messageId)) {
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            [, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
+            ;[, errorCallback, requestCommandName] = this.getCachedRequest(messageType, messageId)!
             // Reject the deferred promise in case of error at response handling (rejecting an already fulfilled promise is a no-op)
             errorCallback(error as OCPPError, false)
           } else {
@@ -2059,13 +2019,18 @@ export class ChargingStation extends EventEmitter {
             this.requests.delete(messageId)
           }
           break
+        case MessageType.CALL_MESSAGE:
+          ;[, , commandName] = request as IncomingRequest
+          // Send error
+          await this.ocppRequestService.sendError(this, messageId, error as OCPPError, commandName)
+          break
       }
       if (!(error instanceof OCPPError)) {
         logger.warn(
-          `${this.logPrefix()} Error thrown at incoming OCPP command '${
+          `${this.logPrefix()} Error thrown at incoming OCPP command ${
             commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
             // eslint-disable-next-line @typescript-eslint/no-base-to-string
-          }' message '${data.toString()}' handling is not an OCPPError:`,
+          } message '${data.toString()}' handling is not an OCPPError:`,
           error
         )
       }
@@ -2075,7 +2040,9 @@ export class ChargingStation extends EventEmitter {
           // eslint-disable-next-line @typescript-eslint/no-base-to-string
         }' message '${data.toString()}'${
           this.requests.has(messageId)
-            ? ` matching cached request '${JSON.stringify(this.getCachedRequest(messageType, messageId))}'`
+            ? ` matching cached request '${JSON.stringify(
+                this.getCachedRequest(messageType, messageId)
+              )}'`
             : ''
         } processing error:`,
         error
@@ -2083,6 +2050,61 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
+  private async onOpen (): Promise<void> {
+    if (this.isWebSocketConnectionOpened()) {
+      this.emit(ChargingStationEvents.connected)
+      this.emit(ChargingStationEvents.updated)
+      logger.info(
+        `${this.logPrefix()} Connection to OCPP server through ${
+          this.wsConnectionUrl.href
+        } succeeded`
+      )
+      let registrationRetryCount = 0
+      if (!this.inAcceptedState()) {
+        // Send BootNotification
+        do {
+          await this.ocppRequestService.requestHandler<
+            BootNotificationRequest,
+            BootNotificationResponse
+          >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
+            skipBufferingOnError: true,
+          })
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.bootNotificationResponse!.currentTime = convertToDate(
+            this.bootNotificationResponse?.currentTime
+          )!
+          if (!this.inAcceptedState()) {
+            ++registrationRetryCount
+            await sleep(
+              exponentialDelay(
+                registrationRetryCount,
+                this.bootNotificationResponse?.interval != null
+                  ? secondsToMilliseconds(this.bootNotificationResponse.interval)
+                  : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL
+              )
+            )
+          }
+        } while (
+          !this.inAcceptedState() &&
+          (this.stationInfo?.registrationMaxRetries === -1 ||
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            registrationRetryCount <= this.stationInfo!.registrationMaxRetries!)
+        )
+      }
+      if (!this.inAcceptedState()) {
+        logger.error(
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `${this.logPrefix()} Registration failure: maximum retries reached (${registrationRetryCount.toString()}) or retry disabled (${this.stationInfo?.registrationMaxRetries?.toString()})`
+        )
+      }
+      this.emit(ChargingStationEvents.updated)
+    } else {
+      logger.warn(
+        `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.href} failed`
+      )
+    }
+  }
+
   private onPing (): void {
     logger.debug(`${this.logPrefix()} Received a WS ping (rfc6455) from the server`)
   }
@@ -2091,135 +2113,250 @@ export class ChargingStation extends EventEmitter {
     logger.debug(`${this.logPrefix()} Received a WS pong (rfc6455) from the server`)
   }
 
-  private onError (error: WSError): void {
-    this.closeWSConnection()
-    logger.error(`${this.logPrefix()} WebSocket error:`, error)
-  }
-
-  private getEnergyActiveImportRegister (
-    connectorStatus: ConnectorStatus | undefined,
-    rounded = false
-  ): number {
-    if (this.stationInfo?.meteringPerTransaction === true) {
-      return (
-        (rounded
-          ? connectorStatus?.transactionEnergyActiveImportRegisterValue != null
-            ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue)
-            : undefined
-          : connectorStatus?.transactionEnergyActiveImportRegisterValue) ?? 0
+  private async reconnect (): Promise<void> {
+    if (
+      this.stationInfo?.autoReconnectMaxRetries === -1 ||
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries!
+    ) {
+      ++this.wsConnectionRetryCount
+      const reconnectDelay =
+        this.stationInfo?.reconnectExponentialDelay === true
+          ? exponentialDelay(this.wsConnectionRetryCount)
+          : secondsToMilliseconds(this.getConnectionTimeout())
+      const reconnectDelayWithdraw = 1000
+      const reconnectTimeout =
+        reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0
+      logger.error(
+        `${this.logPrefix()} WebSocket connection retry in ${roundTo(
+          reconnectDelay,
+          2
+        ).toString()}ms, timeout ${reconnectTimeout.toString()}ms`
+      )
+      await sleep(reconnectDelay)
+      logger.error(
+        `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}`
+      )
+      this.openWSConnection(
+        {
+          handshakeTimeout: reconnectTimeout,
+        },
+        { closeOpened: true }
+      )
+    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
+      logger.error(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${this.wsConnectionRetryCount.toString()}) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries?.toString()})`
       )
     }
-    return (
-      (rounded
-        ? connectorStatus?.energyActiveImportRegisterValue != null
-          ? Math.round(connectorStatus.energyActiveImportRegisterValue)
-          : undefined
-        : connectorStatus?.energyActiveImportRegisterValue) ?? 0
-    )
   }
 
-  private getUseConnectorId0 (stationTemplate?: ChargingStationTemplate): boolean {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return stationTemplate?.useConnectorId0 ?? Constants.DEFAULT_STATION_INFO.useConnectorId0!
+  private saveAutomaticTransactionGeneratorConfiguration (): void {
+    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
+      this.saveConfiguration()
+    }
   }
 
-  private async stopRunningTransactions (reason?: StopTransactionReason): Promise<void> {
-    if (this.hasEvses) {
-      for (const [evseId, evseStatus] of this.evses) {
-        if (evseId === 0) {
-          continue
+  private saveConfiguration (): void {
+    if (isNotEmptyString(this.configurationFile)) {
+      try {
+        if (!existsSync(dirname(this.configurationFile))) {
+          mkdirSync(dirname(this.configurationFile), { recursive: true })
         }
-        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-          if (connectorStatus.transactionStarted === true) {
-            await this.stopTransactionOnConnector(connectorId, reason)
-          }
+        const configurationFromFile = this.getConfigurationFromFile()
+        let configurationData: ChargingStationConfiguration =
+          configurationFromFile != null
+            ? clone<ChargingStationConfiguration>(configurationFromFile)
+            : {}
+        if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
+          configurationData.stationInfo = this.stationInfo
+        } else {
+          delete configurationData.stationInfo
+        }
+        if (
+          this.stationInfo?.ocppPersistentConfiguration === true &&
+          Array.isArray(this.ocppConfiguration?.configurationKey)
+        ) {
+          configurationData.configurationKey = this.ocppConfiguration.configurationKey
+        } else {
+          delete configurationData.configurationKey
+        }
+        configurationData = mergeDeepRight<ChargingStationConfiguration>(
+          configurationData,
+          buildChargingStationAutomaticTransactionGeneratorConfiguration(
+            this
+          ) as Partial<ChargingStationConfiguration>
+        )
+        if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration !== true) {
+          delete configurationData.automaticTransactionGenerator
+        }
+        if (this.connectors.size > 0) {
+          configurationData.connectorsStatus = buildConnectorsStatus(this)
+        } else {
+          delete configurationData.connectorsStatus
+        }
+        if (this.evses.size > 0) {
+          configurationData.evsesStatus = buildEvsesStatus(this)
+        } else {
+          delete configurationData.evsesStatus
+        }
+        delete configurationData.configurationHash
+        const configurationHash = hash(
+          Constants.DEFAULT_HASH_ALGORITHM,
+          JSON.stringify({
+            automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
+            configurationKey: configurationData.configurationKey,
+            stationInfo: configurationData.stationInfo,
+            ...(this.connectors.size > 0 && {
+              connectorsStatus: configurationData.connectorsStatus,
+            }),
+            ...(this.evses.size > 0 && {
+              evsesStatus: configurationData.evsesStatus,
+            }),
+          } satisfies ChargingStationConfiguration),
+          'hex'
+        )
+        if (this.configurationFileHash !== configurationHash) {
+          AsyncLock.runExclusive(AsyncLockType.configuration, () => {
+            configurationData.configurationHash = configurationHash
+            const measureId = `${FileType.ChargingStationConfiguration} write`
+            const beginId = PerformanceStatistics.beginMeasure(measureId)
+            writeFileSync(
+              this.configurationFile,
+              JSON.stringify(configurationData, undefined, 2),
+              'utf8'
+            )
+            PerformanceStatistics.endMeasure(measureId, beginId)
+            this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash)
+            this.sharedLRUCache.setChargingStationConfiguration(configurationData)
+            this.configurationFileHash = configurationHash
+          }).catch((error: unknown) => {
+            handleFileException(
+              this.configurationFile,
+              FileType.ChargingStationConfiguration,
+              error as NodeJS.ErrnoException,
+              this.logPrefix()
+            )
+          })
+        } else {
+          logger.debug(
+            `${this.logPrefix()} Not saving unchanged charging station configuration file ${
+              this.configurationFile
+            }`
+          )
         }
+      } catch (error) {
+        handleFileException(
+          this.configurationFile,
+          FileType.ChargingStationConfiguration,
+          error as NodeJS.ErrnoException,
+          this.logPrefix()
+        )
       }
     } else {
-      for (const connectorId of this.connectors.keys()) {
-        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          await this.stopTransactionOnConnector(connectorId, reason)
-        }
-      }
-    }
-  }
-
-  // 0 for disabling
-  private getConnectionTimeout (): number {
-    if (getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut) != null) {
-      return convertToInt(
-        getConfigurationKey(this, StandardParametersKey.ConnectionTimeOut)?.value ??
-          Constants.DEFAULT_CONNECTION_TIMEOUT
+      logger.error(
+        `${this.logPrefix()} Trying to save charging station configuration to undefined configuration file`
       )
     }
-    return Constants.DEFAULT_CONNECTION_TIMEOUT
   }
 
-  private getPowerDivider (): number {
-    let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()
-    if (this.stationInfo?.powerSharedByConnectors === true) {
-      powerDivider = this.getNumberOfRunningTransactions()
-    }
-    return powerDivider
+  private saveConnectorsStatus (): void {
+    this.saveConfiguration()
   }
 
-  private getMaximumAmperage (stationInfo?: ChargingStationInfo): number | undefined {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const maximumPower = (stationInfo ?? this.stationInfo!).maximumPower!
-    switch (this.getCurrentOutType(stationInfo)) {
-      case CurrentType.AC:
-        return ACElectricUtils.amperagePerPhaseFromPower(
-          this.getNumberOfPhases(stationInfo),
-          maximumPower / (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
-          this.getVoltageOut(stationInfo)
-        )
-      case CurrentType.DC:
-        return DCElectricUtils.amperage(maximumPower, this.getVoltageOut(stationInfo))
-    }
+  private saveEvsesStatus (): void {
+    this.saveConfiguration()
   }
 
-  private getCurrentOutType (stationInfo?: ChargingStationInfo): CurrentType {
-    return (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (stationInfo ?? this.stationInfo!).currentOutType ??
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      Constants.DEFAULT_STATION_INFO.currentOutType!
-    )
+  private saveStationInfo (): void {
+    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
+      this.saveConfiguration()
+    }
   }
 
-  private getVoltageOut (stationInfo?: ChargingStationInfo): Voltage {
-    return (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (stationInfo ?? this.stationInfo!).voltageOut ??
-      getDefaultVoltageOut(this.getCurrentOutType(stationInfo), this.logPrefix(), this.templateFile)
-    )
+  private readonly sendMessageBuffer = (
+    onCompleteCallback: () => void,
+    messageIdx?: number
+  ): void => {
+    if (this.messageQueue.length > 0) {
+      const message = this.messageQueue[0]
+      let beginId: string | undefined
+      let commandName: RequestCommand | undefined
+      let parsedMessage: ErrorResponse | OutgoingRequest | Response
+      messageIdx ??= 0
+      try {
+        parsedMessage = JSON.parse(message) as ErrorResponse | OutgoingRequest | Response
+      } catch (error) {
+        logger.error(
+          `${this.logPrefix()} Error while parsing buffered OCPP message '${message}' to JSON:`,
+          error
+        )
+        this.messageQueue.shift()
+        this.sendMessageBuffer(onCompleteCallback, messageIdx)
+        return
+      }
+      const [messageType] = parsedMessage
+      const isRequest = messageType === MessageType.CALL_MESSAGE
+      if (isRequest) {
+        ;[, , commandName] = parsedMessage as OutgoingRequest
+        beginId = PerformanceStatistics.beginMeasure(commandName)
+      }
+      this.wsConnection?.send(message, (error?: Error) => {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        isRequest && PerformanceStatistics.endMeasure(commandName!, beginId!)
+        if (error == null) {
+          logger.debug(
+            `${this.logPrefix()} >> Buffered ${getMessageTypeString(messageType)} OCPP message sent '${message}'`
+          )
+          this.messageQueue.shift()
+        } else {
+          logger.error(
+            `${this.logPrefix()} >> Buffered ${getMessageTypeString(messageType)} OCPP message '${message}' send failed:`,
+            error
+          )
+        }
+        // eslint-disable-next-line promise/catch-or-return, @typescript-eslint/no-floating-promises, promise/no-promise-in-callback
+        sleep(exponentialDelay(messageIdx))
+          // eslint-disable-next-line promise/always-return
+          .then(() => {
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            ++messageIdx!
+            this.sendMessageBuffer(onCompleteCallback, messageIdx)
+          })
+      })
+    } else {
+      onCompleteCallback()
+    }
   }
 
-  private getAmperageLimitation (): number | undefined {
-    if (
-      isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
-      getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey) != null
-    ) {
-      return (
-        convertToInt(getConfigurationKey(this, this.stationInfo.amperageLimitationOcppKey)?.value) /
-        getAmperageLimitationUnitDivider(this.stationInfo)
-      )
-    }
+  private setIntervalFlushMessageBuffer (): void {
+    this.flushMessageBufferSetInterval ??= setInterval(() => {
+      if (this.isWebSocketConnectionOpened() && this.inAcceptedState()) {
+        this.flushMessageBuffer()
+      }
+      if (!this.isWebSocketConnectionOpened() || isEmpty(this.messageQueue)) {
+        this.clearIntervalFlushMessageBuffer()
+      }
+    }, Constants.DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL)
   }
 
   private async startMessageSequence (ATGStopAbsoluteDuration?: boolean): Promise<void> {
     if (this.stationInfo?.autoRegister === true) {
       await this.ocppRequestService.requestHandler<
-      BootNotificationRequest,
-      BootNotificationResponse
+        BootNotificationRequest,
+        BootNotificationResponse
       >(this, RequestCommand.BOOT_NOTIFICATION, this.bootNotificationRequest, {
-        skipBufferingOnError: true
+        skipBufferingOnError: true,
       })
     }
     // Start WebSocket ping
-    this.startWebSocketPing()
+    if (this.wsPingSetInterval == null) {
+      this.startWebSocketPing()
+    }
     // Start heartbeat
-    this.startHeartbeat()
+    if (this.heartbeatSetInterval == null) {
+      this.startHeartbeat()
+    }
     // Initialize connectors status
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
@@ -2252,10 +2389,10 @@ export class ChargingStation extends EventEmitter {
     }
     if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
       await this.ocppRequestService.requestHandler<
-      FirmwareStatusNotificationRequest,
-      FirmwareStatusNotificationResponse
+        FirmwareStatusNotificationRequest,
+        FirmwareStatusNotificationResponse
       >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-        status: FirmwareStatus.Installed
+        status: FirmwareStatus.Installed,
       })
       this.stationInfo.firmwareStatus = FirmwareStatus.Installed
     }
@@ -2267,14 +2404,36 @@ export class ChargingStation extends EventEmitter {
     this.flushMessageBuffer()
   }
 
-  private internalStopMessageSequence (): void {
-    // Stop WebSocket ping
-    this.stopWebSocketPing()
-    // Stop heartbeat
-    this.stopHeartbeat()
-    // Stop the ATG
-    if (this.automaticTransactionGenerator?.started === true) {
-      this.stopAutomaticTransactionGenerator()
+  private startWebSocketPing (): void {
+    const webSocketPingInterval = this.getWebSocketPingInterval()
+    if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) {
+      this.wsPingSetInterval = setInterval(() => {
+        if (this.isWebSocketConnectionOpened()) {
+          this.wsConnection?.ping()
+        }
+      }, secondsToMilliseconds(webSocketPingInterval))
+      logger.info(
+        `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
+          webSocketPingInterval
+        )}`
+      )
+    } else if (this.wsPingSetInterval != null) {
+      logger.info(
+        `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
+          webSocketPingInterval
+        )}`
+      )
+    } else {
+      logger.error(
+        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval.toString()}, not starting the WebSocket ping`
+      )
+    }
+  }
+
+  private stopHeartbeat (): void {
+    if (this.heartbeatSetInterval != null) {
+      clearInterval(this.heartbeatSetInterval)
+      delete this.heartbeatSetInterval
     }
   }
 
@@ -2284,7 +2443,7 @@ export class ChargingStation extends EventEmitter {
   ): Promise<void> {
     this.internalStopMessageSequence()
     // Stop ongoing transactions
-    stopTransactions === true && (await this.stopRunningTransactions(reason))
+    stopTransactions && (await this.stopRunningTransactions(reason))
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId > 0) {
@@ -2309,34 +2468,24 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private startWebSocketPing (): void {
-    const webSocketPingInterval =
-      getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval) != null
-        ? convertToInt(
-          getConfigurationKey(this, StandardParametersKey.WebSocketPingInterval)?.value
-        )
-        : 0
-    if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) {
-      this.wsPingSetInterval = setInterval(() => {
-        if (this.isWebSocketConnectionOpened()) {
-          this.wsConnection?.ping()
+  private async stopRunningTransactions (reason?: StopTransactionReason): Promise<void> {
+    if (this.hasEvses) {
+      for (const [evseId, evseStatus] of this.evses) {
+        if (evseId === 0) {
+          continue
         }
-      }, secondsToMilliseconds(webSocketPingInterval))
-      logger.info(
-        `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
-          webSocketPingInterval
-        )}`
-      )
-    } else if (this.wsPingSetInterval != null) {
-      logger.info(
-        `${this.logPrefix()} WebSocket ping already started every ${formatDurationSeconds(
-          webSocketPingInterval
-        )}`
-      )
+        for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+          if (connectorStatus.transactionStarted === true) {
+            await this.stopTransactionOnConnector(connectorId, reason)
+          }
+        }
+      }
     } else {
-      logger.error(
-        `${this.logPrefix()} WebSocket ping interval set to ${webSocketPingInterval}, not starting the WebSocket ping`
-      )
+      for (const connectorId of this.connectors.keys()) {
+        if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
+          await this.stopTransactionOnConnector(connectorId, reason)
+        }
+      }
     }
   }
 
@@ -2347,95 +2496,10 @@ export class ChargingStation extends EventEmitter {
     }
   }
 
-  private getConfiguredSupervisionUrl (): URL {
-    let configuredSupervisionUrl: string
-    const supervisionUrls = this.stationInfo?.supervisionUrls ?? Configuration.getSupervisionUrls()
-    if (isNotEmptyArray(supervisionUrls)) {
-      let configuredSupervisionUrlIndex: number
-      switch (Configuration.getSupervisionUrlDistribution()) {
-        case SupervisionUrlDistribution.RANDOM:
-          configuredSupervisionUrlIndex = Math.floor(secureRandom() * supervisionUrls.length)
-          break
-        case SupervisionUrlDistribution.ROUND_ROBIN:
-        case SupervisionUrlDistribution.CHARGING_STATION_AFFINITY:
-        default:
-          !Object.values(SupervisionUrlDistribution).includes(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            Configuration.getSupervisionUrlDistribution()!
-          ) &&
-            logger.warn(
-              // eslint-disable-next-line @typescript-eslint/no-base-to-string
-              `${this.logPrefix()} Unknown supervision url distribution '${Configuration.getSupervisionUrlDistribution()}' in configuration from values '${SupervisionUrlDistribution.toString()}', defaulting to '${
-                SupervisionUrlDistribution.CHARGING_STATION_AFFINITY
-              }'`
-            )
-          configuredSupervisionUrlIndex = (this.index - 1) % supervisionUrls.length
-          break
-      }
-      configuredSupervisionUrl = supervisionUrls[configuredSupervisionUrlIndex]
-    } else {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      configuredSupervisionUrl = supervisionUrls!
-    }
-    if (isNotEmptyString(configuredSupervisionUrl)) {
-      return new URL(configuredSupervisionUrl)
-    }
-    const errorMsg = 'No supervision url(s) configured'
-    logger.error(`${this.logPrefix()} ${errorMsg}`)
-    throw new BaseError(errorMsg)
-  }
-
-  private stopHeartbeat (): void {
-    if (this.heartbeatSetInterval != null) {
-      clearInterval(this.heartbeatSetInterval)
-      delete this.heartbeatSetInterval
-    }
-  }
-
   private terminateWSConnection (): void {
     if (this.isWebSocketConnectionOpened()) {
       this.wsConnection?.terminate()
       this.wsConnection = null
     }
   }
-
-  private async reconnect (): Promise<void> {
-    if (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.wsConnectionRetryCount < this.stationInfo!.autoReconnectMaxRetries! ||
-      this.stationInfo?.autoReconnectMaxRetries === -1
-    ) {
-      this.wsConnectionRetried = true
-      ++this.wsConnectionRetryCount
-      const reconnectDelay =
-        this.stationInfo?.reconnectExponentialDelay === true
-          ? exponentialDelay(this.wsConnectionRetryCount)
-          : secondsToMilliseconds(this.getConnectionTimeout())
-      const reconnectDelayWithdraw = 1000
-      const reconnectTimeout =
-        reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0
-      logger.error(
-        `${this.logPrefix()} WebSocket connection retry in ${roundTo(
-          reconnectDelay,
-          2
-        )}ms, timeout ${reconnectTimeout}ms`
-      )
-      await sleep(reconnectDelay)
-      logger.error(
-        `${this.logPrefix()} WebSocket connection retry #${this.wsConnectionRetryCount.toString()}`
-      )
-      this.openWSConnection(
-        {
-          handshakeTimeout: reconnectTimeout
-        },
-        { closeOpened: true }
-      )
-    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
-      logger.error(
-        `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
-          this.wsConnectionRetryCount
-        }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})`
-      )
-    }
-  }
 }
index ab32a464fabbcf3ca3ee2e20a7c48d685945f327..b8ec0afc6308343fde19cab82916ed8905eac4a7 100644 (file)
@@ -1,11 +1,11 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
 import { parentPort } from 'node:worker_threads'
-
 import { ThreadWorker } from 'poolifier'
 
-import { BaseError } from '../exception/index.js'
 import type { ChargingStationInfo, ChargingStationWorkerData } from '../types/index.js'
+
+import { BaseError } from '../exception/index.js'
 import { Configuration } from '../utils/index.js'
 import { type WorkerDataError, type WorkerMessage, WorkerMessageEvents } from '../worker/index.js'
 import { ChargingStation } from './ChargingStation.js'
@@ -13,11 +13,11 @@ import { ChargingStation } from './ChargingStation.js'
 export let chargingStationWorker: object
 if (Configuration.workerPoolInUse()) {
   chargingStationWorker = new ThreadWorker<
-  ChargingStationWorkerData,
-  ChargingStationInfo | undefined
+    ChargingStationWorkerData,
+    ChargingStationInfo | undefined
   >((data?: ChargingStationWorkerData): ChargingStationInfo | undefined => {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const { index, templateFile, options } = data!
+    const { index, options, templateFile } = data!
     return new ChargingStation(index, templateFile, options).stationInfo
   })
 } else {
@@ -25,7 +25,7 @@ if (Configuration.workerPoolInUse()) {
   class ChargingStationWorker<Data extends ChargingStationWorkerData> {
     constructor () {
       parentPort?.on('message', (message: WorkerMessage<Data>) => {
-        const { uuid, event, data } = message
+        const { data, event, uuid } = message
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
         if (uuid != null) {
           switch (event) {
@@ -37,21 +37,21 @@ if (Configuration.workerPoolInUse()) {
                   data.options
                 )
                 parentPort?.postMessage({
-                  uuid,
-                  event: WorkerMessageEvents.addedWorkerElement,
                   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                  data: chargingStation.stationInfo!
+                  data: chargingStation.stationInfo!,
+                  event: WorkerMessageEvents.addedWorkerElement,
+                  uuid,
                 } satisfies WorkerMessage<ChargingStationInfo>)
               } catch (error) {
                 parentPort?.postMessage({
-                  uuid,
-                  event: WorkerMessageEvents.workerElementError,
                   data: {
                     event,
-                    name: (error as Error).name,
                     message: (error as Error).message,
-                    stack: (error as Error).stack
-                  }
+                    name: (error as Error).name,
+                    stack: (error as Error).stack,
+                  },
+                  event: WorkerMessageEvents.workerElementError,
+                  uuid,
                 } satisfies WorkerMessage<WorkerDataError>)
               }
               break
index 4b9eb40aa211f9c5d5c651aae0203bc8f330fb74..037f75ebd6e2bad263d60a2c4bd833aa9a3bde54 100644 (file)
@@ -1,18 +1,19 @@
 import type { ConfigurationKey, ConfigurationKeyType } from '../types/index.js'
-import { logger } from '../utils/index.js'
 import type { ChargingStation } from './ChargingStation.js'
 
+import { logger } from '../utils/index.js'
+
+interface AddConfigurationKeyParams {
+  overwrite?: boolean
+  save?: boolean
+}
 interface ConfigurationKeyOptions {
   readonly?: boolean
-  visible?: boolean
   reboot?: boolean
+  visible?: boolean
 }
 interface DeleteConfigurationKeyParams {
-  save?: boolean
   caseInsensitive?: boolean
-}
-interface AddConfigurationKeyParams {
-  overwrite?: boolean
   save?: boolean
 }
 
@@ -39,16 +40,16 @@ export const addConfigurationKey = (
   options = {
     ...{
       readonly: false,
+      reboot: false,
       visible: true,
-      reboot: false
     },
-    ...options
+    ...options,
   }
   params = { ...{ overwrite: false, save: false }, ...params }
   let keyFound = getConfigurationKey(chargingStation, key)
-  if (keyFound != null && params.overwrite === true) {
+  if (keyFound != null && params.overwrite) {
     deleteConfigurationKey(chargingStation, keyFound.key, {
-      save: false
+      save: false,
     })
     keyFound = undefined
   }
@@ -57,11 +58,11 @@ export const addConfigurationKey = (
       key,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       readonly: options.readonly!,
+      reboot: options.reboot,
       value,
       visible: options.visible,
-      reboot: options.reboot
     })
-    params.save === true && chargingStation.saveOcppConfiguration()
+    params.save && chargingStation.saveOcppConfiguration()
   } else {
     logger.error(
       `${chargingStation.logPrefix()} Trying to add an already existing configuration key: %j`,
@@ -98,14 +99,14 @@ export const deleteConfigurationKey = (
   key: ConfigurationKeyType,
   params?: DeleteConfigurationKeyParams
 ): ConfigurationKey[] | undefined => {
-  params = { ...{ save: true, caseInsensitive: false }, ...params }
+  params = { ...{ caseInsensitive: false, save: true }, ...params }
   const keyFound = getConfigurationKey(chargingStation, key, params.caseInsensitive)
   if (keyFound != null) {
     const deletedConfigurationKey = chargingStation.ocppConfiguration?.configurationKey?.splice(
       chargingStation.ocppConfiguration.configurationKey.indexOf(keyFound),
       1
     )
-    params.save === true && chargingStation.saveOcppConfiguration()
+    params.save && chargingStation.saveOcppConfiguration()
     return deletedConfigurationKey
   }
 }
index 735a0b39c9bcf967f69297aa1a66618d2b4f9752..cadae37bffe002041cdcebe35e54ef21aa7af51b 100644 (file)
@@ -1,8 +1,4 @@
-import { createHash, randomBytes } from 'node:crypto'
 import type { EventEmitter } from 'node:events'
-import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path'
-import { env } from 'node:process'
-import { fileURLToPath } from 'node:url'
 
 import chalk from 'chalk'
 import {
@@ -18,10 +14,15 @@ import {
   isDate,
   isPast,
   isWithinInterval,
-  toDate
+  toDate,
 } from 'date-fns'
 import { maxTime } from 'date-fns/constants'
-import { isEmpty } from 'rambda'
+import { hash, randomBytes } from 'node:crypto'
+import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path'
+import { env } from 'node:process'
+import { fileURLToPath } from 'node:url'
+
+import type { ChargingStation } from './ChargingStation.js'
 
 import { BaseError } from '../exception/index.js'
 import {
@@ -31,6 +32,7 @@ import {
   BootReasonEnumType,
   type ChargingProfile,
   ChargingProfileKindType,
+  ChargingProfilePurposeType,
   ChargingRateUnitType,
   type ChargingSchedulePeriod,
   type ChargingStationConfiguration,
@@ -51,7 +53,7 @@ import {
   ReservationTerminationReason,
   StandardParametersKey,
   type SupportedFeatureProfiles,
-  Voltage
+  Voltage,
 } from '../types/index.js'
 import {
   ACElectricUtils,
@@ -61,13 +63,13 @@ import {
   convertToInt,
   DCElectricUtils,
   isArraySorted,
+  isEmpty,
   isNotEmptyArray,
   isNotEmptyString,
   isValidDate,
   logger,
-  secureRandom
+  secureRandom,
 } from '../utils/index.js'
-import type { ChargingStation } from './ChargingStation.js'
 import { getConfigurationKey } from './ConfigurationKeyUtils.js'
 
 const moduleName = 'Helpers'
@@ -157,24 +159,87 @@ export const getHashId = (index: number, stationTemplate: ChargingStationTemplat
     chargePointModel: stationTemplate.chargePointModel,
     chargePointVendor: stationTemplate.chargePointVendor,
     ...(stationTemplate.chargeBoxSerialNumberPrefix != null && {
-      chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix
+      chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
     }),
     ...(stationTemplate.chargePointSerialNumberPrefix != null && {
-      chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix
+      chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
     }),
     ...(stationTemplate.meterSerialNumberPrefix != null && {
-      meterSerialNumber: stationTemplate.meterSerialNumberPrefix
+      meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
     }),
     ...(stationTemplate.meterType != null && {
-      meterType: stationTemplate.meterType
-    })
+      meterType: stationTemplate.meterType,
+    }),
+  }
+  return hash(
+    Constants.DEFAULT_HASH_ALGORITHM,
+    `${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`,
+    'hex'
+  )
+}
+
+export const validateStationInfo = (chargingStation: ChargingStation): void => {
+  if (chargingStation.stationInfo == null || isEmpty(chargingStation.stationInfo)) {
+    throw new BaseError('Missing charging station information')
+  }
+  if (
+    chargingStation.stationInfo.chargingStationId == null ||
+    isEmpty(chargingStation.stationInfo.chargingStationId.trim())
+  ) {
+    throw new BaseError('Missing chargingStationId in stationInfo properties')
+  }
+  const chargingStationId = chargingStation.stationInfo.chargingStationId
+  if (
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    chargingStation.stationInfo.hashId == null ||
+    isEmpty(chargingStation.stationInfo.hashId.trim())
+  ) {
+    throw new BaseError(`${chargingStationId}: Missing hashId in stationInfo properties`)
+  }
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+  if (chargingStation.stationInfo.templateIndex == null) {
+    throw new BaseError(`${chargingStationId}: Missing templateIndex in stationInfo properties`)
+  }
+  if (chargingStation.stationInfo.templateIndex <= 0) {
+    throw new BaseError(
+      `${chargingStationId}: Invalid templateIndex value in stationInfo properties`
+    )
+  }
+  if (
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    chargingStation.stationInfo.templateName == null ||
+    isEmpty(chargingStation.stationInfo.templateName.trim())
+  ) {
+    throw new BaseError(`${chargingStationId}: Missing templateName in stationInfo properties`)
+  }
+  if (chargingStation.stationInfo.maximumPower == null) {
+    throw new BaseError(`${chargingStationId}: Missing maximumPower in stationInfo properties`)
+  }
+  if (chargingStation.stationInfo.maximumPower <= 0) {
+    throw new RangeError(
+      `${chargingStationId}: Invalid maximumPower value in stationInfo properties`
+    )
+  }
+  if (chargingStation.stationInfo.maximumAmperage == null) {
+    throw new BaseError(`${chargingStationId}: Missing maximumAmperage in stationInfo properties`)
+  }
+  if (chargingStation.stationInfo.maximumAmperage <= 0) {
+    throw new RangeError(
+      `${chargingStationId}: Invalid maximumAmperage value in stationInfo properties`
+    )
+  }
+  switch (chargingStation.stationInfo.ocppVersion) {
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      if (isEmpty(chargingStation.evses)) {
+        throw new BaseError(
+          `${chargingStationId}: OCPP 2.0 or superior requires at least one EVSE defined in the charging station template/configuration`
+        )
+      }
   }
-  return createHash(Constants.DEFAULT_HASH_ALGORITHM)
-    .update(`${JSON.stringify(chargingStationInfo)}${getChargingStationId(index, stationTemplate)}`)
-    .digest('hex')
 }
 
-export const checkChargingStation = (
+export const checkChargingStationState = (
   chargingStation: ChargingStation,
   logPrefix: string
 ): boolean => {
@@ -191,14 +256,14 @@ export const getPhaseRotationValue = (
 ): string | undefined => {
   // AC/DC
   if (connectorId === 0 && numberOfPhases === 0) {
-    return `${connectorId}.${ConnectorPhaseRotation.RST}`
+    return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
   } else if (connectorId > 0 && numberOfPhases === 0) {
-    return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`
+    return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
     // AC
   } else if (connectorId >= 0 && numberOfPhases === 1) {
-    return `${connectorId}.${ConnectorPhaseRotation.NotApplicable}`
+    return `${connectorId.toString()}.${ConnectorPhaseRotation.NotApplicable}`
   } else if (connectorId >= 0 && numberOfPhases === 3) {
-    return `${connectorId}.${ConnectorPhaseRotation.RST}`
+    return `${connectorId.toString()}.${ConnectorPhaseRotation.RST}`
   }
 }
 
@@ -288,8 +353,8 @@ export const checkConnectorsConfiguration = (
   templateFile: string
 ): {
   configuredMaxConnectors: number
-  templateMaxConnectors: number
   templateMaxAvailableConnectors: number
+  templateMaxConnectors: number
 } => {
   const configuredMaxConnectors = getConfiguredMaxNumberOfConnectors(stationTemplate)
   checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile)
@@ -306,7 +371,11 @@ export const checkConnectorsConfiguration = (
     )
     stationTemplate.randomConnectors = true
   }
-  return { configuredMaxConnectors, templateMaxConnectors, templateMaxAvailableConnectors }
+  return {
+    configuredMaxConnectors,
+    templateMaxAvailableConnectors,
+    templateMaxConnectors,
+  }
 }
 
 export const checkStationInfoConnectorStatus = (
@@ -317,33 +386,12 @@ export const checkStationInfoConnectorStatus = (
 ): void => {
   if (connectorStatus.status != null) {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
+      `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId.toString()} status configuration defined, undefine it`
     )
     delete connectorStatus.status
   }
 }
 
-export const buildConnectorsMap = (
-  connectors: Record<string, ConnectorStatus>,
-  logPrefix: string,
-  templateFile: string
-): Map<number, ConnectorStatus> => {
-  const connectorsMap = new Map<number, ConnectorStatus>()
-  if (getMaxNumberOfConnectors(connectors) > 0) {
-    for (const connector in connectors) {
-      const connectorStatus = connectors[connector]
-      const connectorId = convertToInt(connector)
-      checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile)
-      connectorsMap.set(connectorId, clone<ConnectorStatus>(connectorStatus))
-    }
-  } else {
-    logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
-    )
-  }
-  return connectorsMap
-}
-
 export const setChargingStationOptions = (
   stationInfo: ChargingStationInfo,
   options?: ChargingStationOptions
@@ -375,6 +423,27 @@ export const setChargingStationOptions = (
   return stationInfo
 }
 
+export const buildConnectorsMap = (
+  connectors: Record<string, ConnectorStatus>,
+  logPrefix: string,
+  templateFile: string
+): Map<number, ConnectorStatus> => {
+  const connectorsMap = new Map<number, ConnectorStatus>()
+  if (getMaxNumberOfConnectors(connectors) > 0) {
+    for (const connector in connectors) {
+      const connectorStatus = connectors[connector]
+      const connectorId = convertToInt(connector)
+      checkStationInfoConnectorStatus(connectorId, connectorStatus, logPrefix, templateFile)
+      connectorsMap.set(connectorId, clone<ConnectorStatus>(connectorStatus))
+    }
+  } else {
+    logger.warn(
+      `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
+    )
+  }
+  return connectorsMap
+}
+
 export const initializeConnectorsMapStatus = (
   connectors: Map<number, ConnectorStatus>,
   logPrefix: string
@@ -382,9 +451,10 @@ export const initializeConnectorsMapStatus = (
   for (const connectorId of connectors.keys()) {
     if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
       logger.warn(
-        `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
-          connectors.get(connectorId)?.transactionId
-        }`
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${logPrefix} Connector id ${connectorId.toString()} at initialization has a transaction started with id ${connectors
+          .get(connectorId)
+          ?.transactionId?.toString()}`
       )
     }
     if (connectorId === 0) {
@@ -401,29 +471,60 @@ export const initializeConnectorsMapStatus = (
   }
 }
 
+export const resetAuthorizeConnectorStatus = (connectorStatus: ConnectorStatus): void => {
+  connectorStatus.idTagLocalAuthorized = false
+  connectorStatus.idTagAuthorized = false
+  delete connectorStatus.localAuthorizeIdTag
+  delete connectorStatus.authorizeIdTag
+}
+
 export const resetConnectorStatus = (connectorStatus: ConnectorStatus | undefined): void => {
   if (connectorStatus == null) {
     return
   }
-  connectorStatus.chargingProfiles =
-    connectorStatus.transactionId != null && isNotEmptyArray(connectorStatus.chargingProfiles)
-      ? connectorStatus.chargingProfiles.filter(
-        chargingProfile => chargingProfile.transactionId !== connectorStatus.transactionId
-      )
-      : []
-  connectorStatus.idTagLocalAuthorized = false
-  connectorStatus.idTagAuthorized = false
+  if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+    connectorStatus.chargingProfiles = connectorStatus.chargingProfiles.filter(
+      chargingProfile =>
+        (chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+          chargingProfile.transactionId != null &&
+          connectorStatus.transactionId != null &&
+          chargingProfile.transactionId !== connectorStatus.transactionId) ||
+        chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+    )
+  }
+  resetAuthorizeConnectorStatus(connectorStatus)
   connectorStatus.transactionRemoteStarted = false
   connectorStatus.transactionStarted = false
   delete connectorStatus.transactionStart
   delete connectorStatus.transactionId
-  delete connectorStatus.localAuthorizeIdTag
-  delete connectorStatus.authorizeIdTag
   delete connectorStatus.transactionIdTag
   connectorStatus.transactionEnergyActiveImportRegisterValue = 0
   delete connectorStatus.transactionBeginMeterValue
 }
 
+export const prepareConnectorStatus = (connectorStatus: ConnectorStatus): ConnectorStatus => {
+  if (connectorStatus.reservation != null) {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    connectorStatus.reservation.expiryDate = convertToDate(connectorStatus.reservation.expiryDate)!
+  }
+  if (isNotEmptyArray(connectorStatus.chargingProfiles)) {
+    connectorStatus.chargingProfiles = connectorStatus.chargingProfiles
+      .filter(
+        chargingProfile =>
+          chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE
+      )
+      .map(chargingProfile => {
+        chargingProfile.chargingSchedule.startSchedule = convertToDate(
+          chargingProfile.chargingSchedule.startSchedule
+        )
+        chargingProfile.validFrom = convertToDate(chargingProfile.validFrom)
+        chargingProfile.validTo = convertToDate(chargingProfile.validTo)
+        return chargingProfile
+      })
+  }
+  return connectorStatus
+}
+
 export const createBootNotificationRequest = (
   stationInfo: ChargingStationInfo,
   bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
@@ -435,43 +536,43 @@ export const createBootNotificationRequest = (
         chargePointModel: stationInfo.chargePointModel,
         chargePointVendor: stationInfo.chargePointVendor,
         ...(stationInfo.chargeBoxSerialNumber != null && {
-          chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber
+          chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
         }),
         ...(stationInfo.chargePointSerialNumber != null && {
-          chargePointSerialNumber: stationInfo.chargePointSerialNumber
+          chargePointSerialNumber: stationInfo.chargePointSerialNumber,
         }),
         ...(stationInfo.firmwareVersion != null && {
-          firmwareVersion: stationInfo.firmwareVersion
+          firmwareVersion: stationInfo.firmwareVersion,
         }),
         ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
         ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
         ...(stationInfo.meterSerialNumber != null && {
-          meterSerialNumber: stationInfo.meterSerialNumber
+          meterSerialNumber: stationInfo.meterSerialNumber,
         }),
         ...(stationInfo.meterType != null && {
-          meterType: stationInfo.meterType
-        })
+          meterType: stationInfo.meterType,
+        }),
       } satisfies OCPP16BootNotificationRequest
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201:
       return {
-        reason: bootReason,
         chargingStation: {
           model: stationInfo.chargePointModel,
           vendorName: stationInfo.chargePointVendor,
           ...(stationInfo.firmwareVersion != null && {
-            firmwareVersion: stationInfo.firmwareVersion
+            firmwareVersion: stationInfo.firmwareVersion,
           }),
           ...(stationInfo.chargeBoxSerialNumber != null && {
-            serialNumber: stationInfo.chargeBoxSerialNumber
+            serialNumber: stationInfo.chargeBoxSerialNumber,
           }),
           ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
             modem: {
               ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
-              ...(stationInfo.imsi != null && { imsi: stationInfo.imsi })
-            }
-          })
-        }
+              ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+            },
+          }),
+        },
+        reason: bootReason,
       } satisfies OCPP20BootNotificationRequest
   }
 }
@@ -481,11 +582,11 @@ export const warnTemplateKeysDeprecation = (
   logPrefix: string,
   templateFile: string
 ): void => {
-  const templateKeys: Array<{ deprecatedKey: string, key?: string }> = [
+  const templateKeys: { deprecatedKey: string; key?: string }[] = [
     { deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' },
     { deprecatedKey: 'authorizationFile', key: 'idTagsFile' },
     { deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' },
-    { deprecatedKey: 'mustAuthorizeAtRemoteStart', key: 'remoteAuthorization' }
+    { deprecatedKey: 'mustAuthorizeAtRemoteStart', key: 'remoteAuthorization' },
   ]
   for (const templateKey of templateKeys) {
     warnDeprecatedTemplateKey(
@@ -520,17 +621,19 @@ export const createSerialNumber = (
   stationTemplate: ChargingStationTemplate,
   stationInfo: ChargingStationInfo,
   params?: {
-    randomSerialNumberUpperCase?: boolean
     randomSerialNumber?: boolean
+    randomSerialNumberUpperCase?: boolean
   }
 ): void => {
-  params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params }
-  const serialNumberSuffix =
-    params.randomSerialNumber === true
-      ? getRandomSerialNumberSuffix({
-        upperCase: params.randomSerialNumberUpperCase
-      })
-      : ''
+  params = {
+    ...{ randomSerialNumber: true, randomSerialNumberUpperCase: true },
+    ...params,
+  }
+  const serialNumberSuffix = params.randomSerialNumber
+    ? getRandomSerialNumberSuffix({
+      upperCase: params.randomSerialNumberUpperCase,
+    })
+    : ''
   isNotEmptyString(stationTemplate.chargePointSerialNumberPrefix) &&
     (stationInfo.chargePointSerialNumber = `${stationTemplate.chargePointSerialNumberPrefix}${serialNumberSuffix}`)
   isNotEmptyString(stationTemplate.chargeBoxSerialNumberPrefix) &&
@@ -576,12 +679,12 @@ export const hasFeatureProfile = (
 export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInfo): number => {
   let unitDivider = 1
   switch (stationInfo.amperageLimitationUnit) {
-    case AmpereUnits.DECI_AMPERE:
-      unitDivider = 10
-      break
     case AmpereUnits.CENTI_AMPERE:
       unitDivider = 100
       break
+    case AmpereUnits.DECI_AMPERE:
+      unitDivider = 10
+      break
     case AmpereUnits.MILLI_AMPERE:
       unitDivider = 1000
       break
@@ -589,80 +692,134 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf
   return unitDivider
 }
 
+const getChargingStationChargingProfiles = (
+  chargingStation: ChargingStation
+): ChargingProfile[] => {
+  return (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+    .filter(
+      chargingProfile =>
+        chargingProfile.chargingProfilePurpose ===
+        ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE
+    )
+    .sort((a, b) => b.stackLevel - a.stackLevel)
+}
+
+export const getChargingStationChargingProfilesLimit = (
+  chargingStation: ChargingStation
+): number | undefined => {
+  const chargingProfiles = getChargingStationChargingProfiles(chargingStation)
+  if (isNotEmptyArray(chargingProfiles)) {
+    const chargingProfilesLimit = getChargingProfilesLimit(chargingStation, 0, chargingProfiles)
+    if (chargingProfilesLimit != null) {
+      const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
+      const chargingStationMaximumPower =
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.stationInfo!.maximumPower!
+      if (limit > chargingStationMaximumPower) {
+        logger.error(
+          `${chargingStation.logPrefix()} ${moduleName}.getChargingStationChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than charging station maximum ${chargingStationMaximumPower.toString()}: %j`,
+          chargingProfilesLimit
+        )
+        return chargingStationMaximumPower
+      }
+      return limit
+    }
+  }
+}
+
 /**
- * Gets the connector cloned charging profiles applying a power limitation
- * and sorted by connector id descending then stack level descending
- *
- * @param chargingStation -
- * @param connectorId -
+ * Gets the connector charging profiles relevant for power limitation shallow cloned
+ * and sorted by priorities
+ * @param chargingStation - Charging station
+ * @param connectorId - Connector id
  * @returns connector charging profiles array
  */
 export const getConnectorChargingProfiles = (
   chargingStation: ChargingStation,
   connectorId: number
 ): ChargingProfile[] => {
-  return clone<ChargingProfile[]>(
-    (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
-      .sort((a, b) => b.stackLevel - a.stackLevel)
-      .concat(
-        (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? []).sort(
-          (a, b) => b.stackLevel - a.stackLevel
+  return (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles ?? [])
+    .slice()
+    .sort((a, b) => {
+      if (
+        a.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE &&
+        b.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
+      ) {
+        return -1
+      } else if (
+        a.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE &&
+        b.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE
+      ) {
+        return 1
+      }
+      return b.stackLevel - a.stackLevel
+    })
+    .concat(
+      (chargingStation.getConnectorStatus(0)?.chargingProfiles ?? [])
+        .filter(
+          chargingProfile =>
+            chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_DEFAULT_PROFILE
         )
-      )
-  )
+        .sort((a, b) => b.stackLevel - a.stackLevel)
+    )
 }
 
-export const getChargingStationConnectorChargingProfilesPowerLimit = (
+export const getConnectorChargingProfilesLimit = (
   chargingStation: ChargingStation,
   connectorId: number
 ): number | undefined => {
-  let limit: number | undefined, chargingProfile: ChargingProfile | undefined
-  // Get charging profiles sorted by connector id then stack level
   const chargingProfiles = getConnectorChargingProfiles(chargingStation, connectorId)
   if (isNotEmptyArray(chargingProfiles)) {
-    const result = getLimitFromChargingProfiles(
+    const chargingProfilesLimit = getChargingProfilesLimit(
       chargingStation,
       connectorId,
-      chargingProfiles,
-      chargingStation.logPrefix()
+      chargingProfiles
     )
-    if (result != null) {
-      limit = result.limit
-      chargingProfile = result.chargingProfile
-      switch (chargingStation.stationInfo?.currentOutType) {
-        case CurrentType.AC:
-          limit =
-            chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
-              ? limit
-              : ACElectricUtils.powerTotal(
-                chargingStation.getNumberOfPhases(),
-                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-                chargingStation.stationInfo.voltageOut!,
-                limit
-              )
-          break
-        case CurrentType.DC:
-          limit =
-            chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
-              ? limit
-              : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
-      }
+    if (chargingProfilesLimit != null) {
+      const limit = buildChargingProfilesLimit(chargingStation, chargingProfilesLimit)
       const connectorMaximumPower =
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         chargingStation.stationInfo!.maximumPower! / chargingStation.powerDivider!
       if (limit > connectorMaximumPower) {
         logger.error(
-          `${chargingStation.logPrefix()} ${moduleName}.getChargingStationConnectorChargingProfilesPowerLimit: Charging profile id ${
-            chargingProfile.chargingProfileId
-          } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
-          result
+          `${chargingStation.logPrefix()} ${moduleName}.getConnectorChargingProfilesLimit: Charging profile id ${chargingProfilesLimit.chargingProfile.chargingProfileId.toString()} limit ${limit.toString()} is greater than connector ${connectorId.toString()} maximum ${connectorMaximumPower.toString()}: %j`,
+          chargingProfilesLimit
         )
-        limit = connectorMaximumPower
+        return connectorMaximumPower
       }
+      return limit
     }
   }
-  return limit
+}
+
+const buildChargingProfilesLimit = (
+  chargingStation: ChargingStation,
+  chargingProfilesLimit: ChargingProfilesLimit
+): number => {
+  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+  const errorMsg = `Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in charging station information, cannot build charging profiles limit`
+  const { chargingProfile, limit } = chargingProfilesLimit
+  switch (chargingStation.stationInfo?.currentOutType) {
+    case CurrentType.AC:
+      return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+        ? limit
+        : ACElectricUtils.powerTotal(
+          chargingStation.getNumberOfPhases(),
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          chargingStation.stationInfo.voltageOut!,
+          limit
+        )
+    case CurrentType.DC:
+      return chargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+        ? limit
+        : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        DCElectricUtils.power(chargingStation.stationInfo.voltageOut!, limit)
+    default:
+      logger.error(
+        `${chargingStation.logPrefix()} ${moduleName}.buildChargingProfilesLimit: ${errorMsg}`
+      )
+      throw new BaseError(errorMsg)
+  }
 }
 
 export const getDefaultVoltageOut = (
@@ -714,11 +871,11 @@ export const waitChargingStationEvents = async (
 
 const getConfiguredMaxNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => {
   let configuredMaxNumberOfConnectors = 0
-  if (isNotEmptyArray(stationTemplate.numberOfConnectors)) {
+  if (isNotEmptyArray<number>(stationTemplate.numberOfConnectors)) {
     const numberOfConnectors = stationTemplate.numberOfConnectors
     configuredMaxNumberOfConnectors =
       numberOfConnectors[Math.floor(secureRandom() * numberOfConnectors.length)]
-  } else if (stationTemplate.numberOfConnectors != null) {
+  } else if (typeof stationTemplate.numberOfConnectors === 'number') {
     configuredMaxNumberOfConnectors = stationTemplate.numberOfConnectors
   } else if (stationTemplate.Connectors != null && stationTemplate.Evses == null) {
     configuredMaxNumberOfConnectors =
@@ -746,7 +903,7 @@ const checkConfiguredMaxConnectors = (
 ): void => {
   if (configuredMaxConnectors <= 0) {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
+      `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors.toString()} connectors`
     )
   }
 }
@@ -775,9 +932,7 @@ const initializeConnectorStatus = (connectorStatus: ConnectorStatus): void => {
   connectorStatus.transactionStarted = false
   connectorStatus.energyActiveImportRegisterValue = 0
   connectorStatus.transactionEnergyActiveImportRegisterValue = 0
-  if (connectorStatus.chargingProfiles == null) {
-    connectorStatus.chargingProfiles = []
-  }
+  connectorStatus.chargingProfiles ??= []
 }
 
 const warnDeprecatedTemplateKey = (
@@ -803,7 +958,7 @@ const convertDeprecatedTemplateKey = (
 ): void => {
   if (template[deprecatedKey as keyof ChargingStationTemplate] != null) {
     if (key != null) {
-      (template as unknown as Record<string, unknown>)[key] =
+      ;(template as unknown as Record<string, unknown>)[key] =
         template[deprecatedKey as keyof ChargingStationTemplate]
     }
     // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
@@ -812,65 +967,71 @@ const convertDeprecatedTemplateKey = (
 }
 
 interface ChargingProfilesLimit {
-  limit: number
   chargingProfile: ChargingProfile
+  limit: number
 }
 
 /**
- * Charging profiles shall already be sorted by connector id descending then stack level descending
- *
+ * Get the charging profiles limit for a connector
+ * Charging profiles shall already be sorted by priorities
  * @param chargingStation -
  * @param connectorId -
  * @param chargingProfiles -
- * @param logPrefix -
  * @returns ChargingProfilesLimit
  */
-const getLimitFromChargingProfiles = (
+const getChargingProfilesLimit = (
   chargingStation: ChargingStation,
   connectorId: number,
-  chargingProfiles: ChargingProfile[],
-  logPrefix: string
+  chargingProfiles: ChargingProfile[]
 ): ChargingProfilesLimit | undefined => {
-  const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`
+  const debugLogMsg = `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profiles limit found: %j`
   const currentDate = new Date()
   const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+  let previousActiveChargingProfile: ChargingProfile | undefined
   for (const chargingProfile of chargingProfiles) {
     const chargingSchedule = chargingProfile.chargingSchedule
     if (chargingSchedule.startSchedule == null) {
       logger.debug(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined. Trying to set it to the connector current transaction start date`
+        `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined. Trying to set it to the connector current transaction start date`
       )
       // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
       chargingSchedule.startSchedule = connectorStatus?.transactionStart
     }
     if (!isDate(chargingSchedule.startSchedule)) {
       logger.warn(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
+        `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
       )
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!
     }
     if (chargingSchedule.duration == null) {
       logger.debug(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined and will be set to the maximum time allowed`
+        `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no duration defined and will be set to the maximum time allowed`
       )
       // OCPP specifies that if duration is not defined, it should be infinite
       chargingSchedule.duration = differenceInSeconds(maxTime, chargingSchedule.startSchedule)
     }
-    if (!prepareChargingProfileKind(connectorStatus, chargingProfile, currentDate, logPrefix)) {
+    if (
+      !prepareChargingProfileKind(
+        connectorStatus,
+        chargingProfile,
+        currentDate,
+        chargingStation.logPrefix()
+      )
+    ) {
       continue
     }
-    if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) {
+    if (!canProceedChargingProfile(chargingProfile, currentDate, chargingStation.logPrefix())) {
       continue
     }
     // Check if the charging profile is active
     if (
       isWithinInterval(currentDate, {
+        end: addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration),
         start: chargingSchedule.startSchedule,
-        end: addSeconds(chargingSchedule.startSchedule, chargingSchedule.duration)
       })
     ) {
-      if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) {
+      if (isNotEmptyArray<ChargingSchedulePeriod>(chargingSchedule.chargingSchedulePeriod)) {
         const chargingSchedulePeriodCompareFn = (
           a: ChargingSchedulePeriod,
           b: ChargingSchedulePeriod
@@ -882,31 +1043,31 @@ const getLimitFromChargingProfiles = (
           )
         ) {
           logger.warn(
-            `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} schedule periods are not sorted by start period`
+            `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} schedule periods are not sorted by start period`
           )
           chargingSchedule.chargingSchedulePeriod.sort(chargingSchedulePeriodCompareFn)
         }
         // Check if the first schedule period startPeriod property is equal to 0
         if (chargingSchedule.chargingSchedulePeriod[0].startPeriod !== 0) {
           logger.error(
-            `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod} is not equal to 0`
+            `${chargingStation.logPrefix()} ${moduleName}.getChargingProfilesLimit: Charging profile id ${chargingProfile.chargingProfileId.toString()} first schedule period start period ${chargingSchedule.chargingSchedulePeriod[0].startPeriod.toString()} is not equal to 0`
           )
           continue
         }
         // Handle only one schedule period
         if (chargingSchedule.chargingSchedulePeriod.length === 1) {
-          const result: ChargingProfilesLimit = {
+          const chargingProfilesLimit: ChargingProfilesLimit = {
+            chargingProfile,
             limit: chargingSchedule.chargingSchedulePeriod[0].limit,
-            chargingProfile
           }
-          logger.debug(debugLogMsg, result)
-          return result
+          logger.debug(debugLogMsg, chargingProfilesLimit)
+          return chargingProfilesLimit
         }
         let previousChargingSchedulePeriod: ChargingSchedulePeriod | undefined
         // Search for the right schedule period
         for (const [
           index,
-          chargingSchedulePeriod
+          chargingSchedulePeriod,
         ] of chargingSchedule.chargingSchedulePeriod.entries()) {
           // Find the right schedule period
           if (
@@ -916,16 +1077,13 @@ const getLimitFromChargingProfiles = (
             )
           ) {
             // Found the schedule period: previous is the correct one
-            const result: ChargingProfilesLimit = {
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              limit: previousChargingSchedulePeriod!.limit,
-              chargingProfile
+            const chargingProfilesLimit: ChargingProfilesLimit = {
+              chargingProfile: previousActiveChargingProfile ?? chargingProfile,
+              limit: previousChargingSchedulePeriod?.limit ?? chargingSchedulePeriod.limit,
             }
-            logger.debug(debugLogMsg, result)
-            return result
+            logger.debug(debugLogMsg, chargingProfilesLimit)
+            return chargingProfilesLimit
           }
-          // Keep a reference to previous one
-          previousChargingSchedulePeriod = chargingSchedulePeriod
           // Handle the last schedule period within the charging profile duration
           if (
             index === chargingSchedule.chargingSchedulePeriod.length - 1 ||
@@ -938,15 +1096,19 @@ const getLimitFromChargingProfiles = (
                 chargingSchedule.startSchedule
               ) > chargingSchedule.duration)
           ) {
-            const result: ChargingProfilesLimit = {
-              limit: previousChargingSchedulePeriod.limit,
-              chargingProfile
+            const chargingProfilesLimit: ChargingProfilesLimit = {
+              chargingProfile,
+              limit: chargingSchedulePeriod.limit,
             }
-            logger.debug(debugLogMsg, result)
-            return result
+            logger.debug(debugLogMsg, chargingProfilesLimit)
+            return chargingProfilesLimit
           }
+          // Keep a reference to previous charging schedule period
+          previousChargingSchedulePeriod = chargingSchedulePeriod
         }
       }
+      // Keep a reference to previous active charging profile
+      previousActiveChargingProfile = chargingProfile
     }
   }
 }
@@ -954,7 +1116,7 @@ const getLimitFromChargingProfiles = (
 export const prepareChargingProfileKind = (
   connectorStatus: ConnectorStatus | undefined,
   chargingProfile: ChargingProfile,
-  currentDate: string | number | Date,
+  currentDate: Date | number | string,
   logPrefix: string
 ): boolean => {
   switch (chargingProfile.chargingProfileKind) {
@@ -967,7 +1129,7 @@ export const prepareChargingProfileKind = (
     case ChargingProfileKindType.RELATIVE:
       if (chargingProfile.chargingSchedule.startSchedule != null) {
         logger.warn(
-          `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`
+          `${logPrefix} ${moduleName}.prepareChargingProfileKind: Relative charging profile id ${chargingProfile.chargingProfileId.toString()} has a startSchedule property defined. It will be ignored or used if the connector has a transaction started`
         )
         delete chargingProfile.chargingSchedule.startSchedule
       }
@@ -982,7 +1144,7 @@ export const prepareChargingProfileKind = (
 
 export const canProceedChargingProfile = (
   chargingProfile: ChargingProfile,
-  currentDate: string | number | Date,
+  currentDate: Date | number | string,
   logPrefix: string
 ): boolean => {
   if (
@@ -990,10 +1152,8 @@ export const canProceedChargingProfile = (
     (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo))
   ) {
     logger.debug(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${
-        chargingProfile.chargingProfileId
-      } is not valid for the current date ${
-        isDate(currentDate) ? currentDate.toISOString() : currentDate
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} is not valid for the current date ${
+        isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
       }`
     )
     return false
@@ -1003,19 +1163,19 @@ export const canProceedChargingProfile = (
     chargingProfile.chargingSchedule.duration == null
   ) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no startSchedule or duration defined`
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule or duration defined`
     )
     return false
   }
   if (!isValidDate(chargingProfile.chargingSchedule.startSchedule)) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has an invalid startSchedule date defined`
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has an invalid startSchedule date defined`
     )
     return false
   }
   if (!Number.isSafeInteger(chargingProfile.chargingSchedule.duration)) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has non integer duration defined`
+      `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId.toString()} has non integer duration defined`
     )
     return false
   }
@@ -1031,7 +1191,7 @@ const canProceedRecurringChargingProfile = (
     chargingProfile.recurrencyKind == null
   ) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`
+      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no recurrencyKind defined`
     )
     return false
   }
@@ -1040,7 +1200,7 @@ const canProceedRecurringChargingProfile = (
     chargingProfile.chargingSchedule.startSchedule == null
   ) {
     logger.error(
-      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no startSchedule defined`
+      `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined`
     )
     return false
   }
@@ -1049,14 +1209,14 @@ const canProceedRecurringChargingProfile = (
 
 /**
  * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
- *
  * @param chargingProfile -
  * @param currentDate -
  * @param logPrefix -
+ * @returns boolean
  */
 const prepareRecurringChargingProfile = (
   chargingProfile: ChargingProfile,
-  currentDate: string | number | Date,
+  currentDate: Date | number | string,
   logPrefix: string
 ): boolean => {
   const chargingSchedule = chargingProfile.chargingSchedule
@@ -1066,9 +1226,9 @@ const prepareRecurringChargingProfile = (
     case RecurrencyKindType.DAILY:
       recurringInterval = {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        start: chargingSchedule.startSchedule!,
+        end: addDays(chargingSchedule.startSchedule!, 1),
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        end: addDays(chargingSchedule.startSchedule!, 1)
+        start: chargingSchedule.startSchedule!,
       }
       checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix)
       if (
@@ -1080,8 +1240,8 @@ const prepareRecurringChargingProfile = (
           differenceInDays(currentDate, recurringInterval.start)
         )
         recurringInterval = {
+          end: addDays(chargingSchedule.startSchedule, 1),
           start: chargingSchedule.startSchedule,
-          end: addDays(chargingSchedule.startSchedule, 1)
         }
         recurringIntervalTranslated = true
       }
@@ -1089,9 +1249,9 @@ const prepareRecurringChargingProfile = (
     case RecurrencyKindType.WEEKLY:
       recurringInterval = {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        start: chargingSchedule.startSchedule!,
+        end: addWeeks(chargingSchedule.startSchedule!, 1),
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        end: addWeeks(chargingSchedule.startSchedule!, 1)
+        start: chargingSchedule.startSchedule!,
       }
       checkRecurringChargingProfileDuration(chargingProfile, recurringInterval, logPrefix)
       if (
@@ -1103,28 +1263,30 @@ const prepareRecurringChargingProfile = (
           differenceInWeeks(currentDate, recurringInterval.start)
         )
         recurringInterval = {
+          end: addWeeks(chargingSchedule.startSchedule, 1),
           start: chargingSchedule.startSchedule,
-          end: addWeeks(chargingSchedule.startSchedule, 1)
         }
         recurringIntervalTranslated = true
       }
       break
     default:
       logger.error(
-        `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId} is not supported`
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${chargingProfile.recurrencyKind} charging profile id ${chargingProfile.chargingProfileId.toString()} is not supported`
       )
   }
   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
   if (recurringIntervalTranslated && !isWithinInterval(currentDate, recurringInterval!)) {
     logger.error(
       `${logPrefix} ${moduleName}.prepareRecurringChargingProfile: Recurring ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         chargingProfile.recurrencyKind
-      } charging profile id ${chargingProfile.chargingProfileId} recurrency time interval [${toDate(
+      } charging profile id ${chargingProfile.chargingProfileId.toString()} recurrency time interval [${toDate(
         recurringInterval?.start as Date
       ).toISOString()}, ${toDate(
         recurringInterval?.end as Date
       ).toISOString()}] has not been properly translated to current date ${
-        isDate(currentDate) ? currentDate.toISOString() : currentDate
+        isDate(currentDate) ? currentDate.toISOString() : currentDate.toString()
       } `
     )
   }
@@ -1140,12 +1302,10 @@ const checkRecurringChargingProfileDuration = (
     logger.warn(
       `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
         chargingProfile.chargingProfileKind
-      } charging profile id ${
-        chargingProfile.chargingProfileId
-      } duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
+      } charging profile id ${chargingProfile.chargingProfileId.toString()} duration is not defined, set it to the recurrency time interval duration ${differenceInSeconds(
         interval.end,
         interval.start
-      )}`
+      ).toString()}`
     )
     chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start)
   } else if (
@@ -1154,12 +1314,10 @@ const checkRecurringChargingProfileDuration = (
     logger.warn(
       `${logPrefix} ${moduleName}.checkRecurringChargingProfileDuration: Recurring ${
         chargingProfile.chargingProfileKind
-      } charging profile id ${chargingProfile.chargingProfileId} duration ${
-        chargingProfile.chargingSchedule.duration
-      } is greater than the recurrency time interval duration ${differenceInSeconds(
+      } charging profile id ${chargingProfile.chargingProfileId.toString()} duration ${chargingProfile.chargingSchedule.duration.toString()} is greater than the recurrency time interval duration ${differenceInSeconds(
         interval.end,
         interval.start
-      )}`
+      ).toString()}`
     )
     chargingProfile.chargingSchedule.duration = differenceInSeconds(interval.end, interval.start)
   }
@@ -1170,7 +1328,7 @@ const getRandomSerialNumberSuffix = (params?: {
   upperCase?: boolean
 }): string => {
   const randomSerialNumberSuffix = randomBytes(params?.randomBytesLength ?? 16).toString('hex')
-  if (params?.upperCase === true) {
+  if (params?.upperCase) {
     return randomSerialNumberSuffix.toUpperCase()
   }
   return randomSerialNumberSuffix
index da4b38bfce79de421343e16f90dd2b6a17e85c6b..9c94872ab081476238b3d8df2b2108d47daf0d54 100644 (file)
@@ -1,5 +1,7 @@
 import { type FSWatcher, readFileSync } from 'node:fs'
 
+import type { ChargingStation } from './ChargingStation.js'
+
 import { FileType, IdTagDistribution } from '../types/index.js'
 import {
   handleFileException,
@@ -7,9 +9,8 @@ import {
   logger,
   logPrefix,
   secureRandom,
-  watchJsonFile
+  watchJsonFile,
 } from '../utils/index.js'
-import type { ChargingStation } from './ChargingStation.js'
 import { getIdTagsFile } from './Helpers.js'
 
 interface IdTagsCacheValueType {
@@ -28,20 +29,21 @@ export class IdTagsCache {
   }
 
   public static getInstance (): IdTagsCache {
-    if (IdTagsCache.instance === null) {
-      IdTagsCache.instance = new IdTagsCache()
-    }
+    IdTagsCache.instance ??= new IdTagsCache()
     return IdTagsCache.instance
   }
 
+  public deleteIdTags (file: string): boolean {
+    return this.deleteIdTagsCache(file) && this.deleteIdTagsCacheIndexes(file)
+  }
+
   /**
    * Gets one idtag from the cache given the distribution
    * Must be called after checking the cache is not an empty array
-   *
    * @param distribution -
    * @param chargingStation -
    * @param connectorId -
-   * @returns
+   * @returns string
    */
   public getIdTag (
     distribution: IdTagDistribution,
@@ -53,12 +55,12 @@ export class IdTagsCache {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const idTagsFile = getIdTagsFile(chargingStation.stationInfo!)!
     switch (distribution) {
+      case IdTagDistribution.CONNECTOR_AFFINITY:
+        return this.getConnectorAffinityIdTag(chargingStation, connectorId)
       case IdTagDistribution.RANDOM:
         return this.getRandomIdTag(hashId, idTagsFile)
       case IdTagDistribution.ROUND_ROBIN:
         return this.getRoundRobinIdTag(hashId, idTagsFile)
-      case IdTagDistribution.CONNECTOR_AFFINITY:
-        return this.getConnectorAffinityIdTag(chargingStation, connectorId)
       default:
         return this.getRoundRobinIdTag(hashId, idTagsFile)
     }
@@ -67,9 +69,8 @@ export class IdTagsCache {
   /**
    * Gets all idtags from the cache
    * Must be called after checking the cache is not an empty array
-   *
    * @param file -
-   * @returns
+   * @returns string[] | undefined
    */
   public getIdTags (file: string): string[] | undefined {
     if (!this.hasIdTagsCache(file)) {
@@ -78,8 +79,61 @@ export class IdTagsCache {
     return this.getIdTagsCache(file)
   }
 
-  public deleteIdTags (file: string): boolean {
-    return this.deleteIdTagsCache(file) && this.deleteIdTagsCacheIndexes(file)
+  private deleteIdTagsCache (file: string): boolean {
+    this.idTagsCaches.get(file)?.idTagsFileWatcher?.close()
+    return this.idTagsCaches.delete(file)
+  }
+
+  private deleteIdTagsCacheIndexes (file: string): boolean {
+    const deleted: boolean[] = []
+    for (const [key] of this.idTagsCachesAddressableIndexes) {
+      if (key.startsWith(file)) {
+        deleted.push(this.idTagsCachesAddressableIndexes.delete(key))
+      }
+    }
+    return !deleted.some(value => !value)
+  }
+
+  private getConnectorAffinityIdTag (chargingStation: ChargingStation, connectorId: number): string {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const file = getIdTagsFile(chargingStation.stationInfo!)!
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const idTags = this.getIdTags(file)!
+    const addressableKey = this.getIdTagsCacheIndexesAddressableKey(
+      file,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.stationInfo!.hashId
+    )
+    this.idTagsCachesAddressableIndexes.set(
+      addressableKey,
+      (chargingStation.index - 1 + (connectorId - 1)) % idTags.length
+    )
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)!]
+  }
+
+  private getIdTagsCache (file: string): string[] | undefined {
+    return this.idTagsCaches.get(file)?.idTags
+  }
+
+  private getIdTagsCacheIndexesAddressableKey (prefix: string, uid: string): string {
+    return `${prefix}${uid}`
+  }
+
+  private getIdTagsFromFile (file: string): string[] {
+    if (isNotEmptyString(file)) {
+      try {
+        return JSON.parse(readFileSync(file, 'utf8')) as string[]
+      } catch (error) {
+        handleFileException(
+          file,
+          FileType.Authorization,
+          error as NodeJS.ErrnoException,
+          this.logPrefix(file)
+        )
+      }
+    }
+    return []
   }
 
   private getRandomIdTag (hashId: string, file: string): string {
@@ -107,28 +161,14 @@ export class IdTagsCache {
     return idTag
   }
 
-  private getConnectorAffinityIdTag (chargingStation: ChargingStation, connectorId: number): string {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const file = getIdTagsFile(chargingStation.stationInfo!)!
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const idTags = this.getIdTags(file)!
-    const addressableKey = this.getIdTagsCacheIndexesAddressableKey(
-      file,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      chargingStation.stationInfo!.hashId
-    )
-    this.idTagsCachesAddressableIndexes.set(
-      addressableKey,
-      (chargingStation.index - 1 + (connectorId - 1)) % idTags.length
-    )
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return idTags[this.idTagsCachesAddressableIndexes.get(addressableKey)!]
-  }
-
   private hasIdTagsCache (file: string): boolean {
     return this.idTagsCaches.has(file)
   }
 
+  private readonly logPrefix = (file: string): string => {
+    return logPrefix(` Id tags cache for id tags file '${file}' |`)
+  }
+
   private setIdTagsCache (file: string, idTags: string[]): Map<string, IdTagsCacheValueType> {
     return this.idTagsCaches.set(file, {
       idTags,
@@ -151,56 +191,13 @@ export class IdTagsCache {
                 error as NodeJS.ErrnoException,
                 this.logPrefix(file),
                 {
-                  throwError: false
+                  throwError: false,
                 }
               )
             }
           }
         }
-      )
+      ),
     })
   }
-
-  private getIdTagsCache (file: string): string[] | undefined {
-    return this.idTagsCaches.get(file)?.idTags
-  }
-
-  private deleteIdTagsCache (file: string): boolean {
-    this.idTagsCaches.get(file)?.idTagsFileWatcher?.close()
-    return this.idTagsCaches.delete(file)
-  }
-
-  private deleteIdTagsCacheIndexes (file: string): boolean {
-    const deleted: boolean[] = []
-    for (const [key] of this.idTagsCachesAddressableIndexes) {
-      if (key.startsWith(file)) {
-        deleted.push(this.idTagsCachesAddressableIndexes.delete(key))
-      }
-    }
-    return !deleted.some(value => !value)
-  }
-
-  private getIdTagsCacheIndexesAddressableKey (prefix: string, uid: string): string {
-    return `${prefix}${uid}`
-  }
-
-  private getIdTagsFromFile (file: string): string[] {
-    if (isNotEmptyString(file)) {
-      try {
-        return JSON.parse(readFileSync(file, 'utf8')) as string[]
-      } catch (error) {
-        handleFileException(
-          file,
-          FileType.Authorization,
-          error as NodeJS.ErrnoException,
-          this.logPrefix(file)
-        )
-      }
-    }
-    return []
-  }
-
-  private readonly logPrefix = (file: string): string => {
-    return logPrefix(` Id tags cache for id tags file '${file}' |`)
-  }
 }
index 0da4558d4d46f4c4bd3d98f92eb2a7677856517d..2a28f4323cf08a0f59807a3e24a3ef85eef28268 100644 (file)
@@ -1,19 +1,19 @@
 import { LRUMapWithDelete as LRUCache } from 'mnemonist'
-import { isEmpty } from 'rambda'
 
 import type { ChargingStationConfiguration, ChargingStationTemplate } from '../types/index.js'
-import { isNotEmptyArray, isNotEmptyString } from '../utils/index.js'
+
+import { isEmpty, isNotEmptyArray, isNotEmptyString } from '../utils/index.js'
 import { Bootstrap } from './Bootstrap.js'
 
 enum CacheType {
+  chargingStationConfiguration = 'chargingStationConfiguration',
   chargingStationTemplate = 'chargingStationTemplate',
-  chargingStationConfiguration = 'chargingStationConfiguration'
 }
 
-type CacheValueType = ChargingStationTemplate | ChargingStationConfiguration
+type CacheValueType = ChargingStationConfiguration | ChargingStationTemplate
 
 export class SharedLRUCache {
-  private static instance: SharedLRUCache | null = null
+  private static instance: null | SharedLRUCache = null
   private readonly lruCache: LRUCache<string, CacheValueType>
 
   private constructor () {
@@ -25,26 +25,20 @@ export class SharedLRUCache {
   }
 
   public static getInstance (): SharedLRUCache {
-    if (SharedLRUCache.instance === null) {
-      SharedLRUCache.instance = new SharedLRUCache()
-    }
+    SharedLRUCache.instance ??= new SharedLRUCache()
     return SharedLRUCache.instance
   }
 
-  public hasChargingStationConfiguration (chargingStationConfigurationHash: string): boolean {
-    return this.has(this.getChargingStationConfigurationKey(chargingStationConfigurationHash))
+  public clear (): void {
+    this.lruCache.clear()
   }
 
-  public setChargingStationConfiguration (
-    chargingStationConfiguration: ChargingStationConfiguration
-  ): void {
-    if (this.isChargingStationConfigurationCacheable(chargingStationConfiguration)) {
-      this.set(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.getChargingStationConfigurationKey(chargingStationConfiguration.configurationHash!),
-        chargingStationConfiguration
-      )
-    }
+  public deleteChargingStationConfiguration (chargingStationConfigurationHash: string): void {
+    this.delete(this.getChargingStationConfigurationKey(chargingStationConfigurationHash))
+  }
+
+  public deleteChargingStationTemplate (chargingStationTemplateHash: string): void {
+    this.delete(this.getChargingStationTemplateKey(chargingStationTemplateHash))
   }
 
   public getChargingStationConfiguration (
@@ -55,14 +49,32 @@ export class SharedLRUCache {
     ) as ChargingStationConfiguration
   }
 
-  public deleteChargingStationConfiguration (chargingStationConfigurationHash: string): void {
-    this.delete(this.getChargingStationConfigurationKey(chargingStationConfigurationHash))
+  public getChargingStationTemplate (chargingStationTemplateHash: string): ChargingStationTemplate {
+    return this.get(
+      this.getChargingStationTemplateKey(chargingStationTemplateHash)
+    ) as ChargingStationTemplate
+  }
+
+  public hasChargingStationConfiguration (chargingStationConfigurationHash: string): boolean {
+    return this.has(this.getChargingStationConfigurationKey(chargingStationConfigurationHash))
   }
 
   public hasChargingStationTemplate (chargingStationTemplateHash: string): boolean {
     return this.has(this.getChargingStationTemplateKey(chargingStationTemplateHash))
   }
 
+  public setChargingStationConfiguration (
+    chargingStationConfiguration: ChargingStationConfiguration
+  ): void {
+    if (this.isChargingStationConfigurationCacheable(chargingStationConfiguration)) {
+      this.set(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.getChargingStationConfigurationKey(chargingStationConfiguration.configurationHash!),
+        chargingStationConfiguration
+      )
+    }
+  }
+
   public setChargingStationTemplate (chargingStationTemplate: ChargingStationTemplate): void {
     this.set(
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -71,18 +83,12 @@ export class SharedLRUCache {
     )
   }
 
-  public getChargingStationTemplate (chargingStationTemplateHash: string): ChargingStationTemplate {
-    return this.get(
-      this.getChargingStationTemplateKey(chargingStationTemplateHash)
-    ) as ChargingStationTemplate
-  }
-
-  public deleteChargingStationTemplate (chargingStationTemplateHash: string): void {
-    this.delete(this.getChargingStationTemplateKey(chargingStationTemplateHash))
+  private delete (key: string): void {
+    this.lruCache.delete(key)
   }
 
-  public clear (): void {
-    this.lruCache.clear()
+  private get (key: string): CacheValueType | undefined {
+    return this.lruCache.get(key)
   }
 
   private getChargingStationConfigurationKey (hash: string): string {
@@ -97,18 +103,6 @@ export class SharedLRUCache {
     return this.lruCache.has(key)
   }
 
-  private get (key: string): CacheValueType | undefined {
-    return this.lruCache.get(key)
-  }
-
-  private set (key: string, value: CacheValueType): void {
-    this.lruCache.set(key, value)
-  }
-
-  private delete (key: string): void {
-    this.lruCache.delete(key)
-  }
-
   private isChargingStationConfigurationCacheable (
     chargingStationConfiguration: ChargingStationConfiguration
   ): boolean {
@@ -123,4 +117,8 @@ export class SharedLRUCache {
       isNotEmptyString(chargingStationConfiguration.configurationHash)
     )
   }
+
+  private set (key: string, value: CacheValueType): void {
+    this.lruCache.set(key, value)
+  }
 }
index b5fc1cef13cc76b3f8e5250227485594c55e4210..649c057f24482a6799798a3fadb3144878014542 100644 (file)
@@ -1,5 +1,6 @@
 import { secondsToMilliseconds } from 'date-fns'
-import { isEmpty } from 'rambda'
+
+import type { ChargingStation } from '../ChargingStation.js'
 
 import { BaseError, type OCPPError } from '../../exception/index.js'
 import {
@@ -35,160 +36,135 @@ import {
   type StatusNotificationRequest,
   type StatusNotificationResponse,
   type StopTransactionRequest,
-  type StopTransactionResponse
+  type StopTransactionResponse,
 } from '../../types/index.js'
-import { Constants, convertToInt, isAsyncFunction, logger } from '../../utils/index.js'
-import type { ChargingStation } from '../ChargingStation.js'
+import { Constants, convertToInt, isAsyncFunction, isEmpty, logger } from '../../utils/index.js'
 import { getConfigurationKey } from '../ConfigurationKeyUtils.js'
 import { buildMeterValue } from '../ocpp/index.js'
 import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js'
 
 const moduleName = 'ChargingStationWorkerBroadcastChannel'
 
+type CommandHandler = (
+  requestPayload?: BroadcastChannelRequestPayload
+  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
+) => CommandResponse | Promise<CommandResponse | void> | void
+
 type CommandResponse =
-  | EmptyObject
-  | StartTransactionResponse
-  | StopTransactionResponse
   | AuthorizeResponse
   | BootNotificationResponse
-  | HeartbeatResponse
   | DataTransferResponse
-
-type CommandHandler = (
-  requestPayload?: BroadcastChannelRequestPayload
-  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
-) => Promise<CommandResponse | void> | CommandResponse | void
+  | EmptyObject
+  | HeartbeatResponse
+  | StartTransactionResponse
+  | StopTransactionResponse
 
 export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
-  private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>
   private readonly chargingStation: ChargingStation
+  private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>
 
   constructor (chargingStation: ChargingStation) {
     super()
     const requestParams: RequestParams = {
-      throwError: true
+      throwError: true,
     }
     this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
       [
-        BroadcastChannelProcedureName.START_CHARGING_STATION,
-        () => {
-          this.chargingStation.start()
-        }
-      ],
-      [
-        BroadcastChannelProcedureName.STOP_CHARGING_STATION,
-        async () => {
-          await this.chargingStation.stop()
-        }
+        BroadcastChannelProcedureName.AUTHORIZE,
+        async (requestPayload?: BroadcastChannelRequestPayload) =>
+          await this.chargingStation.ocppRequestService.requestHandler<
+            AuthorizeRequest,
+            AuthorizeResponse
+          >(
+            this.chargingStation,
+            RequestCommand.AUTHORIZE,
+            requestPayload as AuthorizeRequest,
+            requestParams
+          ),
       ],
       [
-        BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS,
+        BroadcastChannelProcedureName.BOOT_NOTIFICATION,
         async (requestPayload?: BroadcastChannelRequestPayload) => {
-          await this.chargingStation.delete(requestPayload?.deleteConfiguration as boolean)
-        }
-      ],
-      [
-        BroadcastChannelProcedureName.OPEN_CONNECTION,
-        () => {
-          this.chargingStation.openWSConnection()
-        }
+          return await this.chargingStation.ocppRequestService.requestHandler<
+            BootNotificationRequest,
+            BootNotificationResponse
+          >(
+            this.chargingStation,
+            RequestCommand.BOOT_NOTIFICATION,
+            {
+              ...this.chargingStation.bootNotificationRequest,
+              ...requestPayload,
+            } as BootNotificationRequest,
+            {
+              skipBufferingOnError: true,
+              throwError: true,
+            }
+          )
+        },
       ],
       [
         BroadcastChannelProcedureName.CLOSE_CONNECTION,
         () => {
           this.chargingStation.closeWSConnection()
-        }
-      ],
-      [
-        BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
-        (requestPayload?: BroadcastChannelRequestPayload) => {
-          this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds)
-        }
+        },
       ],
       [
-        BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
-        (requestPayload?: BroadcastChannelRequestPayload) => {
-          this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds)
-        }
-      ],
-      [
-        BroadcastChannelProcedureName.SET_SUPERVISION_URL,
-        (requestPayload?: BroadcastChannelRequestPayload) => {
-          this.chargingStation.setSupervisionUrl(requestPayload?.url as string)
-        }
-      ],
-      [
-        BroadcastChannelProcedureName.START_TRANSACTION,
-        async (requestPayload?: BroadcastChannelRequestPayload) =>
-          await this.chargingStation.ocppRequestService.requestHandler<
-          StartTransactionRequest,
-          StartTransactionResponse
-          >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload, requestParams)
-      ],
-      [
-        BroadcastChannelProcedureName.STOP_TRANSACTION,
+        BroadcastChannelProcedureName.DATA_TRANSFER,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          StopTransactionRequest,
-          StartTransactionResponse
+            DataTransferRequest,
+            DataTransferResponse
           >(
             this.chargingStation,
-            RequestCommand.STOP_TRANSACTION,
-            {
-              meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
-                requestPayload?.transactionId,
-                true
-              ),
-              ...requestPayload
-            },
+            RequestCommand.DATA_TRANSFER,
+            requestPayload as DataTransferRequest,
             requestParams
-          )
+          ),
       ],
       [
-        BroadcastChannelProcedureName.AUTHORIZE,
-        async (requestPayload?: BroadcastChannelRequestPayload) =>
-          await this.chargingStation.ocppRequestService.requestHandler<
-          AuthorizeRequest,
-          AuthorizeResponse
-          >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload, requestParams)
+        BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS,
+        async (requestPayload?: BroadcastChannelRequestPayload) => {
+          await this.chargingStation.delete(requestPayload?.deleteConfiguration as boolean)
+        },
       ],
       [
-        BroadcastChannelProcedureName.BOOT_NOTIFICATION,
-        async (requestPayload?: BroadcastChannelRequestPayload) => {
-          this.chargingStation.bootNotificationResponse =
-            await this.chargingStation.ocppRequestService.requestHandler<
-            BootNotificationRequest,
-            BootNotificationResponse
-            >(
-              this.chargingStation,
-              RequestCommand.BOOT_NOTIFICATION,
-              {
-                ...this.chargingStation.bootNotificationRequest,
-                ...requestPayload
-              },
-              {
-                skipBufferingOnError: true,
-                throwError: true
-              }
-            )
-          return this.chargingStation.bootNotificationResponse
-        }
+        BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
+        async (requestPayload?: BroadcastChannelRequestPayload) =>
+          await this.chargingStation.ocppRequestService.requestHandler<
+            DiagnosticsStatusNotificationRequest,
+            DiagnosticsStatusNotificationResponse
+          >(
+            this.chargingStation,
+            RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+            requestPayload as DiagnosticsStatusNotificationRequest,
+            requestParams
+          ),
       ],
       [
-        BroadcastChannelProcedureName.STATUS_NOTIFICATION,
+        BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          StatusNotificationRequest,
-          StatusNotificationResponse
-          >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload, requestParams)
+            FirmwareStatusNotificationRequest,
+            FirmwareStatusNotificationResponse
+          >(
+            this.chargingStation,
+            RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+            requestPayload as FirmwareStatusNotificationRequest,
+            requestParams
+          ),
       ],
       [
         BroadcastChannelProcedureName.HEARTBEAT,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          HeartbeatRequest,
-          HeartbeatResponse
-          >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload, requestParams)
+            HeartbeatRequest,
+            HeartbeatResponse
+          >(
+            this.chargingStation,
+            RequestCommand.HEARTBEAT,
+            requestPayload as HeartbeatRequest,
+            requestParams
+          ),
       ],
       [
         BroadcastChannelProcedureName.METER_VALUES,
@@ -198,8 +174,8 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
             StandardParametersKey.MeterValueSampleInterval
           )
           return await this.chargingStation.ocppRequestService.requestHandler<
-          MeterValuesRequest,
-          MeterValuesResponse
+            MeterValuesRequest,
+            MeterValuesResponse
           >(
             this.chargingStation,
             RequestCommand.METER_VALUES,
@@ -215,118 +191,111 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
                   configuredMeterValueSampleInterval != null
                     ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
                     : Constants.DEFAULT_METER_VALUES_INTERVAL
-                )
+                ),
               ],
-              ...requestPayload
-            },
+              ...requestPayload,
+            } as MeterValuesRequest,
             requestParams
           )
-        }
+        },
       ],
       [
-        BroadcastChannelProcedureName.DATA_TRANSFER,
+        BroadcastChannelProcedureName.OPEN_CONNECTION,
+        () => {
+          this.chargingStation.openWSConnection()
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.SET_SUPERVISION_URL,
+        (requestPayload?: BroadcastChannelRequestPayload) => {
+          this.chargingStation.setSupervisionUrl(requestPayload?.url as string)
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
+        (requestPayload?: BroadcastChannelRequestPayload) => {
+          this.chargingStation.startAutomaticTransactionGenerator(requestPayload?.connectorIds)
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.START_CHARGING_STATION,
+        () => {
+          this.chargingStation.start()
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.START_TRANSACTION,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          DataTransferRequest,
-          DataTransferResponse
-          >(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload, requestParams)
+            StartTransactionRequest,
+            StartTransactionResponse
+          >(
+            this.chargingStation,
+            RequestCommand.START_TRANSACTION,
+            requestPayload as StartTransactionRequest,
+            requestParams
+          ),
       ],
       [
-        BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
+        BroadcastChannelProcedureName.STATUS_NOTIFICATION,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          DiagnosticsStatusNotificationRequest,
-          DiagnosticsStatusNotificationResponse
+            StatusNotificationRequest,
+            StatusNotificationResponse
           >(
             this.chargingStation,
-            RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-            requestPayload,
+            RequestCommand.STATUS_NOTIFICATION,
+            requestPayload as StatusNotificationRequest,
             requestParams
-          )
+          ),
       ],
       [
-        BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
+        BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
+        (requestPayload?: BroadcastChannelRequestPayload) => {
+          this.chargingStation.stopAutomaticTransactionGenerator(requestPayload?.connectorIds)
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.STOP_CHARGING_STATION,
+        async () => {
+          await this.chargingStation.stop()
+        },
+      ],
+      [
+        BroadcastChannelProcedureName.STOP_TRANSACTION,
         async (requestPayload?: BroadcastChannelRequestPayload) =>
           await this.chargingStation.ocppRequestService.requestHandler<
-          FirmwareStatusNotificationRequest,
-          FirmwareStatusNotificationResponse
+            StopTransactionRequest,
+            StartTransactionResponse
           >(
             this.chargingStation,
-            RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
-            requestPayload,
+            RequestCommand.STOP_TRANSACTION,
+            {
+              meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
+                requestPayload?.transactionId,
+                true
+              ),
+              ...requestPayload,
+            } as StopTransactionRequest,
             requestParams
-          )
-      ]
+          ),
+      ],
     ])
     this.chargingStation = chargingStation
     this.onmessage = this.requestHandler.bind(this) as (message: unknown) => void
     this.onmessageerror = this.messageErrorHandler.bind(this) as (message: unknown) => void
   }
 
-  private requestHandler (messageEvent: MessageEvent): void {
-    const validatedMessageEvent = this.validateMessageEvent(messageEvent)
-    if (validatedMessageEvent === false) {
-      return
-    }
-    if (this.isResponse(validatedMessageEvent.data)) {
-      return
-    }
-    const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest
-    if (
-      requestPayload.hashIds != null &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      !requestPayload.hashIds.includes(this.chargingStation.stationInfo!.hashId)
-    ) {
-      return
-    }
-    if (requestPayload.hashId != null) {
-      logger.error(
-        `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
-      )
-      return
-    }
-    let responsePayload: BroadcastChannelResponsePayload | undefined
-    this.commandHandler(command, requestPayload)
-      .then(commandResponse => {
-        if (commandResponse == null || isEmpty(commandResponse)) {
-          responsePayload = {
-            hashId: this.chargingStation.stationInfo?.hashId,
-            status: ResponseStatus.SUCCESS
-          }
-        } else {
-          responsePayload = this.commandResponseToResponsePayload(
-            command,
-            requestPayload,
-            commandResponse
-          )
-        }
-      })
-      .catch((error: unknown) => {
-        logger.error(
-          `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
-          error
-        )
-        responsePayload = {
-          hashId: this.chargingStation.stationInfo?.hashId,
-          status: ResponseStatus.FAILURE,
-          command,
-          requestPayload,
-          errorMessage: (error as OCPPError).message,
-          errorStack: (error as OCPPError).stack,
-          errorDetails: (error as OCPPError).details
-        } satisfies BroadcastChannelResponsePayload
-      })
-      .finally(() => {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.sendResponse([uuid, responsePayload!])
-      })
-  }
-
-  private messageErrorHandler (messageEvent: MessageEvent): void {
-    logger.error(
-      `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
-      messageEvent
-    )
+  private cleanRequestPayload (
+    command: BroadcastChannelProcedureName,
+    requestPayload: BroadcastChannelRequestPayload
+  ): void {
+    delete requestPayload.hashId
+    delete requestPayload.hashIds
+    ![
+      BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
+      BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
+    ].includes(command) && delete requestPayload.connectorIds
   }
 
   private async commandHandler (
@@ -351,18 +320,6 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
     throw new BaseError(`Unknown worker broadcast channel command: '${command}'`)
   }
 
-  private cleanRequestPayload (
-    command: BroadcastChannelProcedureName,
-    requestPayload: BroadcastChannelRequestPayload
-  ): void {
-    delete requestPayload.hashId
-    delete requestPayload.hashIds
-    ![
-      BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
-      BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR
-    ].includes(command) && delete requestPayload.connectorIds
-  }
-
   private commandResponseToResponsePayload (
     command: BroadcastChannelProcedureName,
     requestPayload: BroadcastChannelRequestPayload,
@@ -372,15 +329,15 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
     if (responseStatus === ResponseStatus.SUCCESS) {
       return {
         hashId: this.chargingStation.stationInfo?.hashId,
-        status: responseStatus
+        status: responseStatus,
       }
     }
     return {
-      hashId: this.chargingStation.stationInfo?.hashId,
-      status: responseStatus,
       command,
+      commandResponse,
+      hashId: this.chargingStation.stationInfo?.hashId,
       requestPayload,
-      commandResponse
+      status: responseStatus,
     }
   }
 
@@ -389,15 +346,15 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
     commandResponse: CommandResponse
   ): ResponseStatus {
     switch (command) {
+      case BroadcastChannelProcedureName.AUTHORIZE:
       case BroadcastChannelProcedureName.START_TRANSACTION:
       case BroadcastChannelProcedureName.STOP_TRANSACTION:
-      case BroadcastChannelProcedureName.AUTHORIZE:
         if (
           (
             commandResponse as
+              | AuthorizeResponse
               | StartTransactionResponse
               | StopTransactionResponse
-              | AuthorizeResponse
           ).idTagInfo?.status === AuthorizationStatus.ACCEPTED
         ) {
           return ResponseStatus.SUCCESS
@@ -413,14 +370,14 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
           return ResponseStatus.SUCCESS
         }
         return ResponseStatus.FAILURE
-      case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
-      case BroadcastChannelProcedureName.METER_VALUES:
-        if (isEmpty(commandResponse)) {
+      case BroadcastChannelProcedureName.HEARTBEAT:
+        if ('currentTime' in commandResponse) {
           return ResponseStatus.SUCCESS
         }
         return ResponseStatus.FAILURE
-      case BroadcastChannelProcedureName.HEARTBEAT:
-        if ('currentTime' in commandResponse) {
+      case BroadcastChannelProcedureName.METER_VALUES:
+      case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
+        if (isEmpty(commandResponse)) {
           return ResponseStatus.SUCCESS
         }
         return ResponseStatus.FAILURE
@@ -428,4 +385,71 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
         return ResponseStatus.FAILURE
     }
   }
+
+  private messageErrorHandler (messageEvent: MessageEvent): void {
+    logger.error(
+      `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
+      messageEvent
+    )
+  }
+
+  private requestHandler (messageEvent: MessageEvent): void {
+    const validatedMessageEvent = this.validateMessageEvent(messageEvent)
+    if (validatedMessageEvent === false) {
+      return
+    }
+    if (this.isResponse(validatedMessageEvent.data)) {
+      return
+    }
+    const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest
+    if (
+      requestPayload.hashIds != null &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      !requestPayload.hashIds.includes(this.chargingStation.stationInfo!.hashId)
+    ) {
+      return
+    }
+    if (requestPayload.hashId != null) {
+      logger.error(
+        `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' array instead`
+      )
+      return
+    }
+    let responsePayload: BroadcastChannelResponsePayload | undefined
+    this.commandHandler(command, requestPayload)
+      .then(commandResponse => {
+        if (commandResponse == null || isEmpty(commandResponse)) {
+          responsePayload = {
+            hashId: this.chargingStation.stationInfo?.hashId,
+            status: ResponseStatus.SUCCESS,
+          }
+        } else {
+          responsePayload = this.commandResponseToResponsePayload(
+            command,
+            requestPayload,
+            commandResponse
+          )
+        }
+        return undefined
+      })
+      .finally(() => {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.sendResponse([uuid, responsePayload!])
+      })
+      .catch((error: unknown) => {
+        logger.error(
+          `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
+          error
+        )
+        responsePayload = {
+          command,
+          errorDetails: (error as OCPPError).details,
+          errorMessage: (error as OCPPError).message,
+          errorStack: (error as OCPPError).stack,
+          hashId: this.chargingStation.stationInfo?.hashId,
+          requestPayload,
+          status: ResponseStatus.FAILURE,
+        } satisfies BroadcastChannelResponsePayload
+      })
+  }
 }
index ef07b5dac21abda443378f0ad0470b14f0bb4403..48bf16915d39f17cfddc0ac39b6a4f03941326c8 100644 (file)
@@ -1,25 +1,26 @@
+import type { AbstractUIService } from '../ui-server/ui-services/AbstractUIService.js'
+
 import {
   type BroadcastChannelResponse,
   type BroadcastChannelResponsePayload,
   type MessageEvent,
   type ResponsePayload,
-  ResponseStatus
+  ResponseStatus,
 } from '../../types/index.js'
 import { logger } from '../../utils/index.js'
-import type { AbstractUIService } from '../ui-server/ui-services/AbstractUIService.js'
 import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js'
 
 const moduleName = 'UIServiceWorkerBroadcastChannel'
 
 interface Responses {
+  responses: BroadcastChannelResponsePayload[]
   responsesExpected: number
   responsesReceived: number
-  responses: BroadcastChannelResponsePayload[]
 }
 
 export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
-  private readonly uiService: AbstractUIService
   private readonly responses: Map<string, Responses>
+  private readonly uiService: AbstractUIService
 
   constructor (uiService: AbstractUIService) {
     super()
@@ -29,38 +30,6 @@ export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
     this.responses = new Map<string, Responses>()
   }
 
-  private responseHandler (messageEvent: MessageEvent): void {
-    const validatedMessageEvent = this.validateMessageEvent(messageEvent)
-    if (validatedMessageEvent === false) {
-      return
-    }
-    if (this.isRequest(validatedMessageEvent.data)) {
-      return
-    }
-    const [uuid, responsePayload] = validatedMessageEvent.data as BroadcastChannelResponse
-    if (!this.responses.has(uuid)) {
-      this.responses.set(uuid, {
-        responsesExpected: this.uiService.getBroadcastChannelExpectedResponses(uuid),
-        responsesReceived: 1,
-        responses: [responsePayload]
-      })
-    } else if (
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.responses.get(uuid)!.responsesReceived <= this.responses.get(uuid)!.responsesExpected
-    ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ++this.responses.get(uuid)!.responsesReceived
-      this.responses.get(uuid)?.responses.push(responsePayload)
-    }
-    if (
-      this.responses.get(uuid)?.responsesReceived === this.responses.get(uuid)?.responsesExpected
-    ) {
-      this.uiService.sendResponse(uuid, this.buildResponsePayload(uuid))
-      this.responses.delete(uuid)
-      this.uiService.deleteBroadcastChannelRequest(uuid)
-    }
-  }
-
   private buildResponsePayload (uuid: string): ResponsePayload {
     const responsesStatus =
       this.responses
@@ -69,28 +38,31 @@ export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
         ? ResponseStatus.SUCCESS
         : ResponseStatus.FAILURE
     return {
-      status: responsesStatus,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
       hashIdsSucceeded: this.responses
         .get(uuid)
-        ?.responses.map(({ status, hashId }) => {
+        ?.responses.map(({ hashId, status }) => {
           if (hashId != null && status === ResponseStatus.SUCCESS) {
             return hashId
           }
           return undefined
         })
-        .filter(hashId => hashId != null) as string[],
+        .filter(hashId => hashId != null)!,
+      status: responsesStatus,
       ...(responsesStatus === ResponseStatus.FAILURE && {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
         hashIdsFailed: this.responses
           .get(uuid)
-          ?.responses.map(({ status, hashId }) => {
+          ?.responses.map(({ hashId, status }) => {
             if (hashId != null && status === ResponseStatus.FAILURE) {
               return hashId
             }
             return undefined
           })
-          .filter(hashId => hashId != null) as string[]
+          .filter(hashId => hashId != null)!,
       }),
       ...(responsesStatus === ResponseStatus.FAILURE && {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
         responsesFailed: this.responses
           .get(uuid)
           ?.responses.map(response => {
@@ -99,8 +71,8 @@ export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
             }
             return undefined
           })
-          .filter(response => response != null) as BroadcastChannelResponsePayload[]
-      })
+          .filter(response => response != null)!,
+      }),
     }
   }
 
@@ -110,4 +82,36 @@ export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
       messageEvent
     )
   }
+
+  private responseHandler (messageEvent: MessageEvent): void {
+    const validatedMessageEvent = this.validateMessageEvent(messageEvent)
+    if (validatedMessageEvent === false) {
+      return
+    }
+    if (this.isRequest(validatedMessageEvent.data)) {
+      return
+    }
+    const [uuid, responsePayload] = validatedMessageEvent.data as BroadcastChannelResponse
+    if (!this.responses.has(uuid)) {
+      this.responses.set(uuid, {
+        responses: [responsePayload],
+        responsesExpected: this.uiService.getBroadcastChannelExpectedResponses(uuid),
+        responsesReceived: 1,
+      })
+    } else if (
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.responses.get(uuid)!.responsesReceived <= this.responses.get(uuid)!.responsesExpected
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ++this.responses.get(uuid)!.responsesReceived
+      this.responses.get(uuid)?.responses.push(responsePayload)
+    }
+    if (
+      this.responses.get(uuid)?.responsesReceived === this.responses.get(uuid)?.responsesExpected
+    ) {
+      this.uiService.sendResponse(uuid, this.buildResponsePayload(uuid))
+      this.responses.delete(uuid)
+      this.uiService.deleteBroadcastChannelRequest(uuid)
+    }
+  }
 }
index 91c92ac2b41ede0d234b184d127ec6329f1b9427..cb371457816222c32a53038c17f5318b67577385 100644 (file)
@@ -4,8 +4,9 @@ import type {
   BroadcastChannelRequest,
   BroadcastChannelResponse,
   JsonType,
-  MessageEvent
+  MessageEvent,
 } from '../../types/index.js'
+
 import { logger, logPrefix, validateUUID } from '../../utils/index.js'
 
 const moduleName = 'WorkerBroadcastChannel'
@@ -19,10 +20,6 @@ export abstract class WorkerBroadcastChannel extends BroadcastChannel {
     this.postMessage(request)
   }
 
-  protected sendResponse (response: BroadcastChannelResponse): void {
-    this.postMessage(response)
-  }
-
   protected isRequest (message: JsonType[]): boolean {
     return Array.isArray(message) && message.length === 3
   }
@@ -31,7 +28,11 @@ export abstract class WorkerBroadcastChannel extends BroadcastChannel {
     return Array.isArray(message) && message.length === 2
   }
 
-  protected validateMessageEvent (messageEvent: MessageEvent): MessageEvent | false {
+  protected sendResponse (response: BroadcastChannelResponse): void {
+    this.postMessage(response)
+  }
+
+  protected validateMessageEvent (messageEvent: MessageEvent): false | MessageEvent {
     if (!Array.isArray(messageEvent.data)) {
       logger.error(
         `${this.logPrefix(
index 8d8a83c3586549c074e4e4b1feeff5cf905d6b98..747e42eb735cd8e404b029b882ee366734c894a9 100644 (file)
@@ -3,16 +3,17 @@ export type { ChargingStation } from './ChargingStation.js'
 export {
   addConfigurationKey,
   getConfigurationKey,
-  setConfigurationKeyValue
+  setConfigurationKeyValue,
 } from './ConfigurationKeyUtils.js'
 export {
   canProceedChargingProfile,
-  checkChargingStation,
+  checkChargingStationState,
   getConnectorChargingProfiles,
   getIdTagsFile,
   hasFeatureProfile,
   hasReservationExpired,
   prepareChargingProfileKind,
   removeExpiredReservations,
-  resetConnectorStatus
+  resetAuthorizeConnectorStatus,
+  resetConnectorStatus,
 } from './Helpers.js'
index 4a604f1c4635d8d39658c8537ab04f19e49fe49e..f430706a5f434e47cc31a4d0b114eaaaa1a139ca 100644 (file)
@@ -2,114 +2,290 @@ import { type ConnectorStatusTransition, OCPP16ChargePointStatus } from '../../.
 import { OCPPConstants } from '../OCPPConstants.js'
 
 export class OCPP16Constants extends OCPPConstants {
-  static readonly ChargePointStatusChargingStationTransitions: Readonly<
-  ConnectorStatusTransition[]
-  > = Object.freeze([
+  static readonly ChargePointStatusChargingStationTransitions: readonly ConnectorStatusTransition[] =
+    Object.freeze([
       { to: OCPP16ChargePointStatus.Available },
       // { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       { to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Available },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Available,
+      },
       // { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       { to: OCPP16ChargePointStatus.Faulted },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Unavailable }
-    // { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Faulted }
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Available,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      // { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Faulted }
     ])
 
-  static readonly ChargePointStatusConnectorTransitions: Readonly<ConnectorStatusTransition[]> =
+  static readonly ChargePointStatusConnectorTransitions: readonly ConnectorStatusTransition[] =
     Object.freeze([
       { to: OCPP16ChargePointStatus.Available },
       // { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.SuspendedEVSE },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Preparing,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Charging,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
       // { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Finishing },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Available, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Reserved,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.Available,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Available },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.Available,
+      },
       // { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Finishing },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.Charging,
+      },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.Finishing,
+      },
       // { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Reserved },
       // { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Preparing, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Preparing,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Available },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.Available,
+      },
       // { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Preparing },
       // { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Finishing },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.Finishing,
+      },
       // { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Charging, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.Charging,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Available },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.Available,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Charging },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.Charging,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEV, OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Finishing },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.Finishing,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.SuspendedEV, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEV,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Available },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.Available,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.SuspendedEV },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.Charging,
+      },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Finishing },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.Finishing,
+      },
       // { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.SuspendedEVSE, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.SuspendedEVSE,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.Finishing},
-      { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Preparing },
+      {
+        from: OCPP16ChargePointStatus.Finishing,
+        to: OCPP16ChargePointStatus.Available,
+      },
+      {
+        from: OCPP16ChargePointStatus.Finishing,
+        to: OCPP16ChargePointStatus.Preparing,
+      },
       // { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Charging },
       // { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.SuspendedEV },
       // { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.SuspendedEVSE },
       // { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Finishing },
       // { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Finishing, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Finishing,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.Finishing,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       // { to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Preparing },
+      {
+        from: OCPP16ChargePointStatus.Reserved,
+        to: OCPP16ChargePointStatus.Available,
+      },
+      {
+        from: OCPP16ChargePointStatus.Reserved,
+        to: OCPP16ChargePointStatus.Preparing,
+      },
       // { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Charging },
       // { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.SuspendedEV },
       // { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.SuspendedEVSE },
       // { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Finishing },
       // { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Reserved, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Reserved,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
+      {
+        from: OCPP16ChargePointStatus.Reserved,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       { to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.SuspendedEVSE },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Available,
+      },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Preparing,
+      },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Charging,
+      },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
       // { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Finishing },
       // { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Reserved },
       // { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Unavailable },
-      { from: OCPP16ChargePointStatus.Unavailable, to: OCPP16ChargePointStatus.Faulted },
+      {
+        from: OCPP16ChargePointStatus.Unavailable,
+        to: OCPP16ChargePointStatus.Faulted,
+      },
       { to: OCPP16ChargePointStatus.Faulted },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Available },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Preparing },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Charging },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.SuspendedEV },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.SuspendedEVSE },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Finishing },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Reserved },
-      { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Unavailable }
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Available,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Preparing,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Charging,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.SuspendedEV,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.SuspendedEVSE,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Finishing,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Reserved,
+      },
+      {
+        from: OCPP16ChargePointStatus.Faulted,
+        to: OCPP16ChargePointStatus.Unavailable,
+      },
       // { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Faulted }
     ])
 }
index 84fb39c18ca1bb70968a2deab618427c3e17f909..7c0afb283aabf5f24336948b5bd3088220f024bb 100644 (file)
@@ -1,32 +1,32 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
-import { randomInt } from 'node:crypto'
-import { createWriteStream, readdirSync } from 'node:fs'
-import { dirname, extname, join, resolve } from 'node:path'
-import { fileURLToPath, URL } from 'node:url'
-
 import type { ValidateFunction } from 'ajv'
+
 import { Client, type FTPResponse } from 'basic-ftp'
 import {
   addSeconds,
   differenceInSeconds,
   type Interval,
   isDate,
-  secondsToMilliseconds
+  secondsToMilliseconds,
 } from 'date-fns'
 import { maxTime } from 'date-fns/constants'
-import { isEmpty } from 'rambda'
+import { randomInt } from 'node:crypto'
+import { createWriteStream, readdirSync } from 'node:fs'
+import { dirname, extname, join, resolve } from 'node:path'
+import { fileURLToPath, URL } from 'node:url'
 import { create } from 'tar'
 
 import {
   canProceedChargingProfile,
   type ChargingStation,
-  checkChargingStation,
+  checkChargingStationState,
   getConfigurationKey,
   getConnectorChargingProfiles,
   prepareChargingProfileKind,
   removeExpiredReservations,
-  setConfigurationKeyValue
+  resetAuthorizeConnectorStatus,
+  setConfigurationKeyValue,
 } from '../../../charging-station/index.js'
 import { OCPPError } from '../../../exception/index.js'
 import {
@@ -60,7 +60,6 @@ import {
   type OCPP16ClearChargingProfileResponse,
   type OCPP16DataTransferRequest,
   type OCPP16DataTransferResponse,
-  OCPP16DataTransferVendorId,
   OCPP16DiagnosticsStatus,
   type OCPP16DiagnosticsStatusNotificationRequest,
   type OCPP16DiagnosticsStatusNotificationResponse,
@@ -97,7 +96,7 @@ import {
   type SetChargingProfileRequest,
   type SetChargingProfileResponse,
   type UnlockConnectorRequest,
-  type UnlockConnectorResponse
+  type UnlockConnectorResponse,
 } from '../../../types/index.js'
 import {
   Configuration,
@@ -105,11 +104,13 @@ import {
   convertToDate,
   convertToInt,
   formatDurationMilliSeconds,
+  handleIncomingRequestError,
   isAsyncFunction,
+  isEmpty,
   isNotEmptyArray,
   isNotEmptyString,
   logger,
-  sleep
+  sleep,
 } from '../../../utils/index.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
@@ -121,8 +122,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
   protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
 
   private readonly incomingRequestHandlers: Map<
-  OCPP16IncomingRequestCommand,
-  IncomingRequestHandler
+    OCPP16IncomingRequestCommand,
+    IncomingRequestHandler
   >
 
   public constructor () {
@@ -132,282 +133,248 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     super(OCPPVersion.VERSION_16)
     this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
       [
-        OCPP16IncomingRequestCommand.RESET,
-        this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CACHE,
-        this.handleRequestClearCache.bind(this) as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+        this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-        this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+        this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-        this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.CLEAR_CACHE,
+        this.handleRequestClearCache.bind(this) as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-        this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+        this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-        this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.DATA_TRANSFER,
+        this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-        this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
+        this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+        this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-        this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+        this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler,
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-        this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
+        this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-        this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
+        this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-        this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.RESERVE_NOW,
+        this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-        this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.RESET,
+        this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.DATA_TRANSFER,
-        this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+        this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-        this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+        this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.RESERVE_NOW,
-        this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
+        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+        this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
-        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-        this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
-      ]
+        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
+        this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler,
+      ],
     ])
     this.payloadValidateFunctions = new Map<
-    OCPP16IncomingRequestCommand,
-    ValidateFunction<JsonType>
+      OCPP16IncomingRequestCommand,
+      ValidateFunction<JsonType>
     >([
       [
-        OCPP16IncomingRequestCommand.RESET,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
-              'assets/json-schemas/ocpp/1.6/Reset.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
+            'assets/json-schemas/ocpp/1.6/CancelReservation.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CACHE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
-              'assets/json-schemas/ocpp/1.6/ClearCache.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
+            'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
-              'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
+            'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
-              'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CLEAR_CACHE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
+            'assets/json-schemas/ocpp/1.6/ClearCache.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
-              'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
+            'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
-              'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.DATA_TRANSFER,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
+            'assets/json-schemas/ocpp/1.6/DataTransfer.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
-              'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
+            'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
-              'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
+            'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
-              'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
+            'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
-              'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
+            'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
-              'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
+            'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
-              'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.RESERVE_NOW,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
+            'assets/json-schemas/ocpp/1.6/ReserveNow.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
-              'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.RESET,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
+            'assets/json-schemas/ocpp/1.6/Reset.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.DATA_TRANSFER,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
-              'assets/json-schemas/ocpp/1.6/DataTransfer.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
+            'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
-              'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
+            'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.RESERVE_NOW,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
-              'assets/json-schemas/ocpp/1.6/ReserveNow.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
+            'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
-              'assets/json-schemas/ocpp/1.6/CancelReservation.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
+            'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     // Handle incoming request events
     this.on(
@@ -420,26 +387,35 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         if (response.status === GenericStatus.Accepted) {
           const { connectorId, idTag } = request
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
+          chargingStation.getConnectorStatus(connectorId!)!.transactionRemoteStarted = true
           chargingStation.ocppRequestService
             .requestHandler<Partial<OCPP16StartTransactionRequest>, OCPP16StartTransactionResponse>(
-            chargingStation,
-            OCPP16RequestCommand.START_TRANSACTION,
-            {
-              connectorId,
-              idTag
-            }
-          )
+              chargingStation,
+              OCPP16RequestCommand.START_TRANSACTION,
+              {
+                connectorId,
+                idTag,
+              }
+            )
             .then(response => {
-              if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
+              if (response.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
                 logger.debug(
-                  `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
+                  `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                    chargingStation.stationInfo?.chargingStationId
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  }#${connectorId?.toString()} for idTag '${idTag}'`
                 )
               } else {
                 logger.debug(
-                  `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
+                  `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                    chargingStation.stationInfo?.chargingStationId
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  }#${connectorId?.toString()} for idTag '${idTag}'`
                 )
               }
+              return undefined
             })
             .catch((error: unknown) => {
               logger.error(
@@ -465,13 +441,20 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
             .then(response => {
               if (response.status === GenericStatus.Accepted) {
                 logger.debug(
-                  `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
+                  `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                    chargingStation.stationInfo?.chargingStationId
+                  }#${connectorId.toString()} for transaction '${transactionId.toString()}'`
                 )
               } else {
                 logger.debug(
-                  `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
+                  `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                    chargingStation.stationInfo?.chargingStationId
+                  }#${connectorId.toString()} for transaction '${transactionId.toString()}'`
                 )
               }
+              return undefined
             })
             .catch((error: unknown) => {
               logger.error(
@@ -492,7 +475,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
           return
         }
-        const { requestedMessage, connectorId } = request
+        const { connectorId, requestedMessage } = request
         const errorHandler = (error: unknown): void => {
           logger.error(
             `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
@@ -502,86 +485,81 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         switch (requestedMessage) {
           case OCPP16MessageTrigger.BootNotification:
             chargingStation.ocppRequestService
-              .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
-              chargingStation,
-              OCPP16RequestCommand.BOOT_NOTIFICATION,
-              chargingStation.bootNotificationRequest as OCPP16BootNotificationRequest,
-              { skipBufferingOnError: true, triggerMessage: true }
-            )
-              .then(response => {
-                chargingStation.bootNotificationResponse = response
-              })
+              .requestHandler<
+                OCPP16BootNotificationRequest,
+                OCPP16BootNotificationResponse
+              >(chargingStation, OCPP16RequestCommand.BOOT_NOTIFICATION, chargingStation.bootNotificationRequest as OCPP16BootNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
               .catch(errorHandler)
             break
           case OCPP16MessageTrigger.Heartbeat:
             chargingStation.ocppRequestService
               .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
-              chargingStation,
-              OCPP16RequestCommand.HEARTBEAT,
-              undefined,
-              {
-                triggerMessage: true
-              }
-            )
+                chargingStation,
+                OCPP16RequestCommand.HEARTBEAT,
+                undefined,
+                {
+                  triggerMessage: true,
+                }
+              )
               .catch(errorHandler)
             break
           case OCPP16MessageTrigger.StatusNotification:
             if (connectorId != null) {
               chargingStation.ocppRequestService
                 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
-                chargingStation,
-                OCPP16RequestCommand.STATUS_NOTIFICATION,
-                {
-                  connectorId,
-                  errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
-                  status: chargingStation.getConnectorStatus(connectorId)
-                    ?.status as OCPP16ChargePointStatus
-                },
-                {
-                  triggerMessage: true
-                }
-              )
+                  chargingStation,
+                  OCPP16RequestCommand.STATUS_NOTIFICATION,
+                  {
+                    connectorId,
+                    errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+                    status: chargingStation.getConnectorStatus(connectorId)
+                      ?.status as OCPP16ChargePointStatus,
+                  },
+                  {
+                    triggerMessage: true,
+                  }
+                )
                 .catch(errorHandler)
             } else if (chargingStation.hasEvses) {
               for (const evseStatus of chargingStation.evses.values()) {
                 for (const [id, connectorStatus] of evseStatus.connectors) {
                   chargingStation.ocppRequestService
                     .requestHandler<
-                  OCPP16StatusNotificationRequest,
-                  OCPP16StatusNotificationResponse
+                      OCPP16StatusNotificationRequest,
+                      OCPP16StatusNotificationResponse
+                    >(
+                      chargingStation,
+                      OCPP16RequestCommand.STATUS_NOTIFICATION,
+                      {
+                        connectorId: id,
+                        errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+                        status: connectorStatus.status as OCPP16ChargePointStatus,
+                      },
+                      {
+                        triggerMessage: true,
+                      }
+                    )
+                    .catch(errorHandler)
+                }
+              }
+            } else {
+              for (const [id, connectorStatus] of chargingStation.connectors) {
+                chargingStation.ocppRequestService
+                  .requestHandler<
+                    OCPP16StatusNotificationRequest,
+                    OCPP16StatusNotificationResponse
                   >(
                     chargingStation,
                     OCPP16RequestCommand.STATUS_NOTIFICATION,
                     {
                       connectorId: id,
                       errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
-                      status: connectorStatus.status as OCPP16ChargePointStatus
+                      status: connectorStatus.status as OCPP16ChargePointStatus,
                     },
                     {
-                      triggerMessage: true
+                      triggerMessage: true,
                     }
                   )
-                    .catch(errorHandler)
-                }
-              }
-            } else {
-              for (const [id, connectorStatus] of chargingStation.connectors) {
-                chargingStation.ocppRequestService
-                  .requestHandler<
-                OCPP16StatusNotificationRequest,
-                OCPP16StatusNotificationResponse
-                >(
-                  chargingStation,
-                  OCPP16RequestCommand.STATUS_NOTIFICATION,
-                  {
-                    connectorId: id,
-                    errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
-                    status: connectorStatus.status as OCPP16ChargePointStatus
-                  },
-                  {
-                    triggerMessage: true
-                  }
-                )
                   .catch(errorHandler)
               }
             }
@@ -592,6 +570,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
     messageId: string,
@@ -617,7 +596,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       )
     }
     if (
-      chargingStation.isRegistered() ||
+      chargingStation.inAcceptedState() ||
+      chargingStation.inPendingState() ||
       (chargingStation.stationInfo?.ocppStrictCompliance === false &&
         chargingStation.inUnknownState())
     ) {
@@ -647,7 +627,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
+          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
             commandPayload,
             undefined,
             2
@@ -679,111 +659,103 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     this.emit(commandName, chargingStation, commandPayload, response)
   }
 
-  private validatePayload (
+  private async handleRequestCancelReservation (
     chargingStation: ChargingStation,
-    commandName: OCPP16IncomingRequestCommand,
-    commandPayload: JsonType
-  ): boolean {
-    if (this.payloadValidateFunctions.has(commandName)) {
-      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
+    commandPayload: OCPP16CancelReservationRequest
+  ): Promise<GenericResponse> {
+    if (
+      !OCPP16ServiceUtils.checkFeatureProfile(
+        chargingStation,
+        OCPP16SupportedFeatureProfiles.Reservation,
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION
+      )
+    ) {
+      return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
+    }
+    try {
+      const { reservationId } = commandPayload
+      const reservation = chargingStation.getReservationBy('reservationId', reservationId)
+      if (reservation == null) {
+        logger.debug(
+          `${chargingStation.logPrefix()} Reservation with id ${reservationId.toString()} does not exist on charging station`
+        )
+        return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
+      }
+      await chargingStation.removeReservation(
+        reservation,
+        ReservationTerminationReason.RESERVATION_CANCELED
+      )
+      return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
+    } catch (error) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return handleIncomingRequestError<GenericResponse>(
+        chargingStation,
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        error as Error,
+        {
+          errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED,
+        }
+      )!
     }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
-
-  // Simulate charging station restart
-  private handleRequestReset (
-    chargingStation: ChargingStation,
-    commandPayload: ResetRequest
-  ): GenericResponse {
-    const { type } = commandPayload
-    chargingStation
-      .reset(`${type}Reset` as OCPP16StopTransactionReason)
-      .catch(Constants.EMPTY_FUNCTION)
-    logger.info(
-      `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        chargingStation.stationInfo!.resetTime!
-      )}`
-    )
-    return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
   }
 
-  private async handleRequestUnlockConnector (
+  private async handleRequestChangeAvailability (
     chargingStation: ChargingStation,
-    commandPayload: UnlockConnectorRequest
-  ): Promise<UnlockConnectorResponse> {
-    const { connectorId } = commandPayload
+    commandPayload: OCPP16ChangeAvailabilityRequest
+  ): Promise<OCPP16ChangeAvailabilityResponse> {
+    const { connectorId, type } = commandPayload
     if (!chargingStation.hasConnector(connectorId)) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
+        `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId.toString()}`
       )
-      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
+      return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
     }
+    const chargePointStatus: OCPP16ChargePointStatus =
+      type === OCPP16AvailabilityType.Operative
+        ? OCPP16ChargePointStatus.Available
+        : OCPP16ChargePointStatus.Unavailable
     if (connectorId === 0) {
-      logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
-      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
-    }
-    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
-      const stopResponse = await chargingStation.stopTransactionOnConnector(
-        connectorId,
-        OCPP16StopTransactionReason.UNLOCK_COMMAND
-      )
-      if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
-        return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
+      let response: OCPP16ChangeAvailabilityResponse | undefined
+      if (chargingStation.hasEvses) {
+        for (const evseStatus of chargingStation.evses.values()) {
+          response = await OCPP16ServiceUtils.changeAvailability(
+            chargingStation,
+            [...evseStatus.connectors.keys()],
+            chargePointStatus,
+            type
+          )
+        }
+      } else {
+        response = await OCPP16ServiceUtils.changeAvailability(
+          chargingStation,
+          [...chargingStation.connectors.keys()],
+          chargePointStatus,
+          type
+        )
       }
-      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
-    }
-    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-      chargingStation,
-      connectorId,
-      OCPP16ChargePointStatus.Available
-    )
-    return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
-  }
-
-  private handleRequestGetConfiguration (
-    chargingStation: ChargingStation,
-    commandPayload: GetConfigurationRequest
-  ): GetConfigurationResponse {
-    const { key } = commandPayload
-    const configurationKey: OCPPConfigurationKey[] = []
-    const unknownKey: string[] = []
-    if (key == null) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
-        if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
-          continue
-        }
-        configurationKey.push({
-          key: configKey.key,
-          readonly: configKey.readonly,
-          value: configKey.value
-        })
-      }
-    } else if (isNotEmptyArray(key)) {
-      for (const k of key) {
-        const keyFound = getConfigurationKey(chargingStation, k, true)
-        if (keyFound != null) {
-          if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
-            continue
-          }
-          configurationKey.push({
-            key: keyFound.key,
-            readonly: keyFound.readonly,
-            value: keyFound.value
-          })
-        } else {
-          unknownKey.push(k)
-        }
+      return response!
+    } else if (
+      connectorId > 0 &&
+      (chargingStation.isChargingStationAvailable() ||
+        (!chargingStation.isChargingStationAvailable() &&
+          type === OCPP16AvailabilityType.Inoperative))
+    ) {
+      if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.getConnectorStatus(connectorId)!.availability = type
+        return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
       }
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.availability = type
+      await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+        chargingStation,
+        connectorId,
+        chargePointStatus
+      )
+      return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
     }
-    return {
-      configurationKey,
-      unknownKey
-    }
+    return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
   }
 
   private handleRequestChangeConfiguration (
@@ -835,6 +807,25 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       ) {
         chargingStation.restartWebSocketPing()
       }
+      if (
+        (keyToChange.key as OCPP16StandardParametersKey) ===
+          OCPP16StandardParametersKey.MeterValueSampleInterval &&
+        chargingStation.getNumberOfRunningTransactions() > 0 &&
+        valueChanged
+      ) {
+        for (
+          let connectorId = 1;
+          connectorId <= chargingStation.getNumberOfConnectors();
+          connectorId++
+        ) {
+          if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
+            chargingStation.restartMeterValues(
+              connectorId,
+              secondsToMilliseconds(convertToInt(value))
+            )
+          }
+        }
+      }
       if (keyToChange.reboot === true) {
         return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
       }
@@ -843,72 +834,88 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
   }
 
-  private handleRequestSetChargingProfile (
+  private handleRequestClearChargingProfile (
     chargingStation: ChargingStation,
-    commandPayload: SetChargingProfileRequest
-  ): SetChargingProfileResponse {
+    commandPayload: OCPP16ClearChargingProfileRequest
+  ): OCPP16ClearChargingProfileResponse {
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
         OCPP16SupportedFeatureProfiles.SmartCharging,
-        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
-      )
-    ) {
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
-    }
-    const { connectorId, csChargingProfiles } = commandPayload
-    if (!chargingStation.hasConnector(connectorId)) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
       )
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
-    }
-    if (
-      csChargingProfiles.chargingProfilePurpose ===
-        OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
-      connectorId !== 0
-    ) {
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
-    }
-    if (
-      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
-      connectorId === 0
     ) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
-      )
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+      return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
     }
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    if (
-      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
-      connectorId > 0 &&
-      connectorStatus?.transactionStarted === false
-    ) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
-      )
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+    const { connectorId } = commandPayload
+    if (connectorId != null) {
+      if (!chargingStation.hasConnector(connectorId)) {
+        logger.error(
+          `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId.toString()}`
+        )
+        return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
+      }
+      const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+      if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
+        connectorStatus.chargingProfiles = []
+        logger.debug(
+          `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId.toString()}`
+        )
+        return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
+      }
+    } else {
+      let clearedCP = false
+      if (chargingStation.hasEvses) {
+        for (const evseStatus of chargingStation.evses.values()) {
+          for (const status of evseStatus.connectors.values()) {
+            const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
+              chargingStation,
+              commandPayload,
+              status.chargingProfiles
+            )
+            if (clearedConnectorCP && !clearedCP) {
+              clearedCP = true
+            }
+          }
+        }
+      } else {
+        for (const id of chargingStation.connectors.keys()) {
+          const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
+            chargingStation,
+            commandPayload,
+            chargingStation.getConnectorStatus(id)?.chargingProfiles
+          )
+          if (clearedConnectorCP && !clearedCP) {
+            clearedCP = true
+          }
+        }
+      }
+      if (clearedCP) {
+        return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
+      }
     }
-    if (
-      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
-      connectorId > 0 &&
-      connectorStatus?.transactionStarted === true &&
-      csChargingProfiles.transactionId !== connectorStatus.transactionId
-    ) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
-          csChargingProfiles.transactionId
-        } than the started transaction id ${connectorStatus.transactionId}`
-      )
-      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+    return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
+  }
+
+  private handleRequestDataTransfer (
+    chargingStation: ChargingStation,
+    commandPayload: OCPP16DataTransferRequest
+  ): OCPP16DataTransferResponse {
+    const { vendorId } = commandPayload
+    try {
+      if (vendorId === chargingStation.stationInfo?.chargePointVendor) {
+        return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
+      }
+      return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
+    } catch (error) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return handleIncomingRequestError<OCPP16DataTransferResponse>(
+        chargingStation,
+        OCPP16IncomingRequestCommand.DATA_TRANSFER,
+        error as Error,
+        { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
+      )!
     }
-    OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
-    logger.debug(
-      `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
-      csChargingProfiles
-    )
-    return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
   }
 
   private handleRequestGetCompositeSchedule (
@@ -924,16 +931,16 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     ) {
       return OCPP16Constants.OCPP_RESPONSE_REJECTED
     }
-    const { connectorId, duration, chargingRateUnit } = commandPayload
+    const { chargingRateUnit, connectorId, duration } = commandPayload
     if (!chargingStation.hasConnector(connectorId)) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
+        `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId.toString()}`
       )
       return OCPP16Constants.OCPP_RESPONSE_REJECTED
     }
     if (connectorId === 0) {
       logger.error(
-        `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
+        `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId.toString()} is not yet supported`
       )
       return OCPP16Constants.OCPP_RESPONSE_REJECTED
     }
@@ -951,10 +958,10 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     }
     const currentDate = new Date()
     const compositeScheduleInterval: Interval = {
+      end: addSeconds(currentDate, duration),
       start: currentDate,
-      end: addSeconds(currentDate, duration)
     }
-    // Get charging profiles sorted by connector id then stack level
+    // FIXME: add and handle charging station charging profiles
     const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
       chargingStation,
       connectorId
@@ -964,18 +971,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     for (const chargingProfile of chargingProfiles) {
       if (chargingProfile.chargingSchedule.startSchedule == null) {
         logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
-            chargingProfile.chargingProfileId
-          } has no startSchedule defined. Trying to set it to the connector current transaction start date`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no startSchedule defined. Trying to set it to the connector current transaction start date`
         )
         // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
         chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
       }
       if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
         logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
-            chargingProfile.chargingProfileId
-          } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${chargingProfile.chargingProfileId.toString()} startSchedule property is not a Date instance. Trying to convert it to a Date instance`
         )
         chargingProfile.chargingSchedule.startSchedule = convertToDate(
           chargingProfile.chargingSchedule.startSchedule
@@ -983,9 +986,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       }
       if (chargingProfile.chargingSchedule.duration == null) {
         logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
-            chargingProfile.chargingProfileId
-          } has no duration defined and will be set to the maximum time allowed`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${chargingProfile.chargingProfileId.toString()} has no duration defined and will be set to the maximum time allowed`
         )
         // OCPP specifies that if duration is not defined, it should be infinite
         chargingProfile.chargingSchedule.duration = differenceInSeconds(
@@ -1022,137 +1023,210 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     }
     if (compositeSchedule != null) {
       return {
-        status: GenericStatus.Accepted,
-        scheduleStart: compositeSchedule.startSchedule,
+        chargingSchedule: compositeSchedule,
         connectorId,
-        chargingSchedule: compositeSchedule
+        scheduleStart: compositeSchedule.startSchedule,
+        status: GenericStatus.Accepted,
       }
     }
     return OCPP16Constants.OCPP_RESPONSE_REJECTED
   }
 
-  private handleRequestClearChargingProfile (
+  private handleRequestGetConfiguration (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16ClearChargingProfileRequest
-  ): OCPP16ClearChargingProfileResponse {
-    if (
-      !OCPP16ServiceUtils.checkFeatureProfile(
-        chargingStation,
-        OCPP16SupportedFeatureProfiles.SmartCharging,
-        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
-      )
-    ) {
-      return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
-    }
-    const { connectorId } = commandPayload
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (!chargingStation.hasConnector(connectorId!)) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
-      )
-      return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
-    }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
-    if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
-      connectorStatus.chargingProfiles = []
-      logger.debug(
-        `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
-      )
-      return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
-    }
-    if (connectorId == null) {
-      let clearedCP = false
-      if (chargingStation.hasEvses) {
-        for (const evseStatus of chargingStation.evses.values()) {
-          for (const status of evseStatus.connectors.values()) {
-            clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
-              chargingStation,
-              commandPayload,
-              status.chargingProfiles
-            )
-          }
-        }
-      } else {
-        for (const id of chargingStation.connectors.keys()) {
-          clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
-            chargingStation,
-            commandPayload,
-            chargingStation.getConnectorStatus(id)?.chargingProfiles
-          )
+    commandPayload: GetConfigurationRequest
+  ): GetConfigurationResponse {
+    const { key } = commandPayload
+    const configurationKey: OCPPConfigurationKey[] = []
+    const unknownKey: string[] = []
+    if (key == null || isEmpty(key)) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
+        if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
+          continue
         }
+        configurationKey.push({
+          key: configKey.key,
+          readonly: configKey.readonly,
+          value: configKey.value,
+        })
       }
-      if (clearedCP) {
-        return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
+    } else if (isNotEmptyArray(key)) {
+      for (const k of key) {
+        const keyFound = getConfigurationKey(chargingStation, k, true)
+        if (keyFound != null) {
+          if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
+            continue
+          }
+          configurationKey.push({
+            key: keyFound.key,
+            readonly: keyFound.readonly,
+            value: keyFound.value,
+          })
+        } else {
+          unknownKey.push(k)
+        }
       }
     }
-    return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
+    return {
+      configurationKey,
+      unknownKey,
+    }
   }
 
-  private async handleRequestChangeAvailability (
+  private async handleRequestGetDiagnostics (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16ChangeAvailabilityRequest
-  ): Promise<OCPP16ChangeAvailabilityResponse> {
-    const { connectorId, type } = commandPayload
-    if (!chargingStation.hasConnector(connectorId)) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
-      )
-      return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
+    commandPayload: GetDiagnosticsRequest
+  ): Promise<GetDiagnosticsResponse> {
+    if (
+      !OCPP16ServiceUtils.checkFeatureProfile(
+        chargingStation,
+        OCPP16SupportedFeatureProfiles.FirmwareManagement,
+        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
+      )
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
+      )
+      return OCPP16Constants.OCPP_RESPONSE_EMPTY
     }
-    const chargePointStatus: OCPP16ChargePointStatus =
-      type === OCPP16AvailabilityType.Operative
-        ? OCPP16ChargePointStatus.Available
-        : OCPP16ChargePointStatus.Unavailable
-    if (connectorId === 0) {
-      let response: OCPP16ChangeAvailabilityResponse | undefined
-      if (chargingStation.hasEvses) {
-        for (const evseStatus of chargingStation.evses.values()) {
-          response = await OCPP16ServiceUtils.changeAvailability(
-            chargingStation,
-            [...evseStatus.connectors.keys()],
-            chargePointStatus,
-            type
+    const { location } = commandPayload
+    const uri = new URL(location)
+    if (uri.protocol.startsWith('ftp:')) {
+      let ftpClient: Client | undefined
+      try {
+        const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
+          ConfigurationSection.log
+        )
+        const logFiles = readdirSync(
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!)))
+        )
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          .filter(file => file.endsWith(extname(logConfiguration.file!)))
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          .map(file => join(dirname(logConfiguration.file!), file))
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
+        create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
+        ftpClient = new Client()
+        const accessResponse = await ftpClient.access({
+          host: uri.hostname,
+          ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
+          ...(isNotEmptyString(uri.username) && { user: uri.username }),
+          ...(isNotEmptyString(uri.password) && { password: uri.password }),
+        })
+        let uploadResponse: FTPResponse | undefined
+        if (accessResponse.code === 220) {
+          ftpClient.trackProgress(info => {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${(
+                info.bytes / 1024
+              ).toString()} bytes transferred from diagnostics archive ${info.name}`
+            )
+            chargingStation.ocppRequestService
+              .requestHandler<
+                OCPP16DiagnosticsStatusNotificationRequest,
+                OCPP16DiagnosticsStatusNotificationResponse
+              >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
+                status: OCPP16DiagnosticsStatus.Uploading,
+              })
+              .catch((error: unknown) => {
+                logger.error(
+                  `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
+                    OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
+                  }'`,
+                  error
+                )
+              })
+          })
+          uploadResponse = await ftpClient.uploadFrom(
+            join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
+            `${uri.pathname}${diagnosticsArchive}`
+          )
+          if (uploadResponse.code === 226) {
+            await chargingStation.ocppRequestService.requestHandler<
+              OCPP16DiagnosticsStatusNotificationRequest,
+              OCPP16DiagnosticsStatusNotificationResponse
+            >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
+              status: OCPP16DiagnosticsStatus.Uploaded,
+            })
+            ftpClient.close()
+            return { fileName: diagnosticsArchive }
+          }
+          throw new OCPPError(
+            ErrorType.GENERIC_ERROR,
+            `Diagnostics transfer failed with error code ${accessResponse.code.toString()}|${uploadResponse.code.toString()}`,
+            OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
           )
         }
-      } else {
-        response = await OCPP16ServiceUtils.changeAvailability(
-          chargingStation,
-          [...chargingStation.connectors.keys()],
-          chargePointStatus,
-          type
+        throw new OCPPError(
+          ErrorType.GENERIC_ERROR,
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `Diagnostics transfer failed with error code ${accessResponse.code.toString()}|${uploadResponse?.code.toString()}`,
+          OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
         )
-      }
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return response!
-    } else if (
-      connectorId > 0 &&
-      (chargingStation.isChargingStationAvailable() ||
-        (!chargingStation.isChargingStationAvailable() &&
-          type === OCPP16AvailabilityType.Inoperative))
-    ) {
-      if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
+      } catch (error) {
+        await chargingStation.ocppRequestService.requestHandler<
+          OCPP16DiagnosticsStatusNotificationRequest,
+          OCPP16DiagnosticsStatusNotificationResponse
+        >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
+          status: OCPP16DiagnosticsStatus.UploadFailed,
+        })
+        ftpClient?.close()
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        chargingStation.getConnectorStatus(connectorId)!.availability = type
-        return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
+        return handleIncomingRequestError<GetDiagnosticsResponse>(
+          chargingStation,
+          OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+          error as Error,
+          { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
+        )!
       }
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      chargingStation.getConnectorStatus(connectorId)!.availability = type
-      await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-        chargingStation,
-        connectorId,
-        chargePointStatus
+    } else {
+      logger.error(
+        `${chargingStation.logPrefix()} Unsupported protocol ${
+          uri.protocol
+        } to transfer the diagnostic logs archive`
       )
-      return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
+      await chargingStation.ocppRequestService.requestHandler<
+        OCPP16DiagnosticsStatusNotificationRequest,
+        OCPP16DiagnosticsStatusNotificationResponse
+      >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
+        status: OCPP16DiagnosticsStatus.UploadFailed,
+      })
+      return OCPP16Constants.OCPP_RESPONSE_EMPTY
     }
-    return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
   }
 
   private async handleRequestRemoteStartTransaction (
     chargingStation: ChargingStation,
     commandPayload: RemoteStartTransactionRequest
   ): Promise<GenericResponse> {
-    const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
+    if (commandPayload.connectorId == null) {
+      for (
+        let connectorId = 1;
+        connectorId <= chargingStation.getNumberOfConnectors();
+        connectorId++
+      ) {
+        if (
+          chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false &&
+          !OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, commandPayload.idTag)
+        ) {
+          commandPayload.connectorId = connectorId
+          break
+        }
+      }
+      if (commandPayload.connectorId == null) {
+        logger.debug(
+          `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
+            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            chargingStation.stationInfo?.chargingStationId
+          }, idTag '${commandPayload.idTag}': no available connector found`
+        )
+        return OCPP16Constants.OCPP_RESPONSE_REJECTED
+      }
+    }
+    const { chargingProfile, connectorId: transactionConnectorId, idTag } = commandPayload
     if (!chargingStation.hasConnector(transactionConnectorId)) {
       return this.notifyRemoteStartTransactionRejected(
         chargingStation,
@@ -1196,44 +1270,14 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       )
     }
     logger.debug(
-      `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${transactionConnectorId}}, idTag '${idTag}'`
+      `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        chargingStation.stationInfo?.chargingStationId
+      }#${transactionConnectorId.toString()}}, idTag '${idTag}'`
     )
     return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
   }
 
-  private notifyRemoteStartTransactionRejected (
-    chargingStation: ChargingStation,
-    connectorId: number,
-    idTag: string
-  ): GenericResponse {
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    logger.debug(
-      `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
-    )
-    return OCPP16Constants.OCPP_RESPONSE_REJECTED
-  }
-
-  private setRemoteStartTransactionChargingProfile (
-    chargingStation: ChargingStation,
-    connectorId: number,
-    chargingProfile: OCPP16ChargingProfile
-  ): boolean {
-    if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
-      OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
-      logger.debug(
-        `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${chargingStation.stationInfo?.chargingStationId}#${connectorId}`,
-        chargingProfile
-      )
-      return true
-    }
-    logger.debug(
-      `${chargingStation.logPrefix()} Not allowed to set ${
-        chargingProfile.chargingProfilePurpose
-      } charging profile(s) at remote start transaction`
-    )
-    return false
-  }
-
   private handleRequestRemoteStopTransaction (
     chargingStation: ChargingStation,
     commandPayload: RemoteStopTransactionRequest
@@ -1241,341 +1285,209 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     const { transactionId } = commandPayload
     if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
       logger.debug(
-        `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
+        `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId.toString()}'`
       )
       return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
     }
     logger.debug(
-      `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
+      `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId.toString()}'`
     )
     return OCPP16Constants.OCPP_RESPONSE_REJECTED
   }
 
-  private handleRequestUpdateFirmware (
+  private async handleRequestReserveNow (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16UpdateFirmwareRequest
-  ): OCPP16UpdateFirmwareResponse {
+    commandPayload: OCPP16ReserveNowRequest
+  ): Promise<OCPP16ReserveNowResponse> {
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
-        OCPP16SupportedFeatureProfiles.FirmwareManagement,
-        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
+        OCPP16SupportedFeatureProfiles.Reservation,
+        OCPP16IncomingRequestCommand.RESERVE_NOW
       )
     ) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
-      )
-      return OCPP16Constants.OCPP_RESPONSE_EMPTY
+      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
-    const { retrieveDate } = commandPayload
-    if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
+    commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
+    const { connectorId, idTag, reservationId } = commandPayload
+    if (!chargingStation.hasConnector(connectorId)) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to reserve a non existing connector id ${connectorId.toString()}`
       )
-      return OCPP16Constants.OCPP_RESPONSE_EMPTY
+      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
-    const now = Date.now()
-    if (retrieveDate.getTime() <= now) {
-      this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
-    } else {
-      setTimeout(() => {
-        this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
-      }, retrieveDate.getTime() - now)
+    if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
+      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
-    return OCPP16Constants.OCPP_RESPONSE_EMPTY
-  }
-
-  private async updateFirmwareSimulation (
-    chargingStation: ChargingStation,
-    maxDelay = 30,
-    minDelay = 15
-  ): Promise<void> {
-    if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
-      return
+    if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
+      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
-    if (chargingStation.hasEvses) {
-      for (const [evseId, evseStatus] of chargingStation.evses) {
-        if (evseId > 0) {
-          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-            if (connectorStatus.transactionStarted === false) {
-              await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-                chargingStation,
-                connectorId,
-                OCPP16ChargePointStatus.Unavailable
-              )
-            }
-          }
-        }
-      }
-    } else {
-      for (const connectorId of chargingStation.connectors.keys()) {
-        if (
-          connectorId > 0 &&
-          chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
-        ) {
-          await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-            chargingStation,
-            connectorId,
-            OCPP16ChargePointStatus.Unavailable
-          )
-        }
-      }
+    if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
+      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
     }
-    await chargingStation.ocppRequestService.requestHandler<
-    OCPP16FirmwareStatusNotificationRequest,
-    OCPP16FirmwareStatusNotificationResponse
-    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-      status: OCPP16FirmwareStatus.Downloading
-    })
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
-    if (
-      chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
-      OCPP16FirmwareStatus.DownloadFailed
-    ) {
-      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
-      await chargingStation.ocppRequestService.requestHandler<
-      OCPP16FirmwareStatusNotificationRequest,
-      OCPP16FirmwareStatusNotificationResponse
-      >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-        status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
-      })
-      chargingStation.stationInfo.firmwareStatus =
-        chargingStation.stationInfo.firmwareUpgrade.failureStatus
-      return
-    }
-    await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
-    await chargingStation.ocppRequestService.requestHandler<
-    OCPP16FirmwareStatusNotificationRequest,
-    OCPP16FirmwareStatusNotificationResponse
-    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-      status: OCPP16FirmwareStatus.Downloaded
-    })
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
-    let wasTransactionsStarted = false
-    let transactionsStarted: boolean
-    do {
-      const runningTransactions = chargingStation.getNumberOfRunningTransactions()
-      if (runningTransactions > 0) {
-        const waitTime = secondsToMilliseconds(15)
-        logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
-            waitTime
-          )} before continuing firmware update simulation`
-        )
-        await sleep(waitTime)
-        transactionsStarted = true
-        wasTransactionsStarted = true
-      } else {
-        if (chargingStation.hasEvses) {
-          for (const [evseId, evseStatus] of chargingStation.evses) {
-            if (evseId > 0) {
-              for (const [connectorId, connectorStatus] of evseStatus.connectors) {
-                if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
-                  await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-                    chargingStation,
-                    connectorId,
-                    OCPP16ChargePointStatus.Unavailable
-                  )
-                }
-              }
-            }
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
+    resetAuthorizeConnectorStatus(connectorStatus)
+    let response: OCPP16ReserveNowResponse
+    try {
+      await removeExpiredReservations(chargingStation)
+      switch (connectorStatus.status) {
+        case OCPP16ChargePointStatus.Charging:
+        case OCPP16ChargePointStatus.Finishing:
+        case OCPP16ChargePointStatus.Preparing:
+        case OCPP16ChargePointStatus.SuspendedEV:
+        case OCPP16ChargePointStatus.SuspendedEVSE:
+          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
+          break
+        case OCPP16ChargePointStatus.Faulted:
+          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
+          break
+        case OCPP16ChargePointStatus.Unavailable:
+          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
+          break
+        case OCPP16ChargePointStatus.Reserved:
+          if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
+            response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
+            break
           }
-        } else {
-          for (const connectorId of chargingStation.connectors.keys()) {
-            if (
-              connectorId > 0 &&
-              chargingStation.getConnectorStatus(connectorId)?.status !==
-                OCPP16ChargePointStatus.Unavailable
-            ) {
-              await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-                chargingStation,
-                connectorId,
-                OCPP16ChargePointStatus.Unavailable
-              )
-            }
+        // eslint-disable-next-line no-fallthrough
+        default:
+          if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
+            response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
+            break
           }
-        }
-        transactionsStarted = false
+          await chargingStation.addReservation({
+            id: commandPayload.reservationId,
+            ...commandPayload,
+          })
+          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
+          break
       }
-    } while (transactionsStarted)
-    !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
-    if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
-      return
-    }
-    await chargingStation.ocppRequestService.requestHandler<
-    OCPP16FirmwareStatusNotificationRequest,
-    OCPP16FirmwareStatusNotificationResponse
-    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-      status: OCPP16FirmwareStatus.Installing
-    })
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
-    if (
-      chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
-      OCPP16FirmwareStatus.InstallationFailed
-    ) {
-      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
-      await chargingStation.ocppRequestService.requestHandler<
-      OCPP16FirmwareStatusNotificationRequest,
-      OCPP16FirmwareStatusNotificationResponse
-      >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
-        status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
-      })
-      chargingStation.stationInfo.firmwareStatus =
-        chargingStation.stationInfo.firmwareUpgrade.failureStatus
-      return
-    }
-    if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
-      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
-      await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
+      return response
+    } catch (error) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      return handleIncomingRequestError<OCPP16ReserveNowResponse>(
+        chargingStation,
+        OCPP16IncomingRequestCommand.RESERVE_NOW,
+        error as Error,
+        { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
+      )!
     }
   }
 
-  private async handleRequestGetDiagnostics (
+  // Simulate charging station restart
+  private handleRequestReset (
     chargingStation: ChargingStation,
-    commandPayload: GetDiagnosticsRequest
-  ): Promise<GetDiagnosticsResponse> {
+    commandPayload: ResetRequest
+  ): GenericResponse {
+    const { type } = commandPayload
+    chargingStation
+      .reset(`${type}Reset` as OCPP16StopTransactionReason)
+      .catch(Constants.EMPTY_FUNCTION)
+    logger.info(
+      `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        chargingStation.stationInfo!.resetTime!
+      )}`
+    )
+    return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
+  }
+
+  private handleRequestSetChargingProfile (
+    chargingStation: ChargingStation,
+    commandPayload: SetChargingProfileRequest
+  ): SetChargingProfileResponse {
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
-        OCPP16SupportedFeatureProfiles.FirmwareManagement,
-        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
+        OCPP16SupportedFeatureProfiles.SmartCharging,
+        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
       )
     ) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
-      )
-      return OCPP16Constants.OCPP_RESPONSE_EMPTY
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
     }
-    const { location } = commandPayload
-    const uri = new URL(location)
-    if (uri.protocol.startsWith('ftp:')) {
-      let ftpClient: Client | undefined
-      try {
-        const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
-          ConfigurationSection.log
-        )
-        const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          .filter(file => file.endsWith(extname(logConfiguration.file!)))
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          .map(file => join(dirname(logConfiguration.file!), file))
-        const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
-        create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
-        ftpClient = new Client()
-        const accessResponse = await ftpClient.access({
-          host: uri.hostname,
-          ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
-          ...(isNotEmptyString(uri.username) && { user: uri.username }),
-          ...(isNotEmptyString(uri.password) && { password: uri.password })
-        })
-        let uploadResponse: FTPResponse | undefined
-        if (accessResponse.code === 220) {
-          ftpClient.trackProgress(info => {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
-                info.bytes / 1024
-              } bytes transferred from diagnostics archive ${info.name}`
-            )
-            chargingStation.ocppRequestService
-              .requestHandler<
-            OCPP16DiagnosticsStatusNotificationRequest,
-            OCPP16DiagnosticsStatusNotificationResponse
-            >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
-              status: OCPP16DiagnosticsStatus.Uploading
-            })
-              .catch((error: unknown) => {
-                logger.error(
-                  `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
-                    OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
-                  }'`,
-                  error
-                )
-              })
-          })
-          uploadResponse = await ftpClient.uploadFrom(
-            join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
-            `${uri.pathname}${diagnosticsArchive}`
-          )
-          if (uploadResponse.code === 226) {
-            await chargingStation.ocppRequestService.requestHandler<
-            OCPP16DiagnosticsStatusNotificationRequest,
-            OCPP16DiagnosticsStatusNotificationResponse
-            >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
-              status: OCPP16DiagnosticsStatus.Uploaded
-            })
-            ftpClient.close()
-            return { fileName: diagnosticsArchive }
-          }
-          throw new OCPPError(
-            ErrorType.GENERIC_ERROR,
-            `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
-            OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
-          )
-        }
-        throw new OCPPError(
-          ErrorType.GENERIC_ERROR,
-          `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
-          OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
-        )
-      } catch (error) {
-        await chargingStation.ocppRequestService.requestHandler<
-        OCPP16DiagnosticsStatusNotificationRequest,
-        OCPP16DiagnosticsStatusNotificationResponse
-        >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
-          status: OCPP16DiagnosticsStatus.UploadFailed
-        })
-        ftpClient?.close()
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        return this.handleIncomingRequestError<GetDiagnosticsResponse>(
-          chargingStation,
-          OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-          error as Error,
-          { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
-        )!
-      }
-    } else {
+    const { connectorId, csChargingProfiles } = commandPayload
+    if (!chargingStation.hasConnector(connectorId)) {
       logger.error(
-        `${chargingStation.logPrefix()} Unsupported protocol ${
-          uri.protocol
-        } to transfer the diagnostic logs archive`
+        `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId.toString()}`
       )
-      await chargingStation.ocppRequestService.requestHandler<
-      OCPP16DiagnosticsStatusNotificationRequest,
-      OCPP16DiagnosticsStatusNotificationResponse
-      >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
-        status: OCPP16DiagnosticsStatus.UploadFailed
-      })
-      return OCPP16Constants.OCPP_RESPONSE_EMPTY
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
     }
-  }
-
-  private handleRequestTriggerMessage (
-    chargingStation: ChargingStation,
-    commandPayload: OCPP16TriggerMessageRequest
-  ): OCPP16TriggerMessageResponse {
-    const { requestedMessage, connectorId } = commandPayload
     if (
-      !OCPP16ServiceUtils.checkFeatureProfile(
-        chargingStation,
-        OCPP16SupportedFeatureProfiles.RemoteTrigger,
-        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
-      ) ||
-      !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
+      csChargingProfiles.chargingProfilePurpose ===
+        OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
+      connectorId !== 0
     ) {
-      return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
     }
     if (
-      !OCPP16ServiceUtils.isConnectorIdValid(
-        chargingStation,
-        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        connectorId!
-      )
+      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
+      connectorId === 0
+    ) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId.toString()}`
+      )
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+    }
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    if (
+      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
+      connectorId > 0 &&
+      connectorStatus?.transactionStarted === false
+    ) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId.toString()} without a started transaction`
+      )
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+    }
+    if (
+      csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
+      connectorId > 0 &&
+      connectorStatus?.transactionStarted === true &&
+      csChargingProfiles.transactionId != null &&
+      csChargingProfiles.transactionId !== connectorStatus.transactionId
+    ) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId.toString()} with a different transaction id ${
+          csChargingProfiles.transactionId.toString()
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        } than the started transaction id ${connectorStatus.transactionId?.toString()}`
+      )
+      return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
+    }
+    OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
+    logger.debug(
+      `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId.toString()}: %j`,
+      csChargingProfiles
+    )
+    return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
+  }
+
+  private handleRequestTriggerMessage (
+    chargingStation: ChargingStation,
+    commandPayload: OCPP16TriggerMessageRequest
+  ): OCPP16TriggerMessageResponse {
+    const { connectorId, requestedMessage } = commandPayload
+    if (
+      !OCPP16ServiceUtils.checkFeatureProfile(
+        chargingStation,
+        OCPP16SupportedFeatureProfiles.RemoteTrigger,
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
+      ) ||
+      !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
+    ) {
+      return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
+    }
+    if (
+      !OCPP16ServiceUtils.isConnectorIdValid(
+        chargingStation,
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        connectorId!
+      )
     ) {
       return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
     }
@@ -1589,136 +1501,285 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
-  private handleRequestDataTransfer (
+  private async handleRequestUnlockConnector (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16DataTransferRequest
-  ): OCPP16DataTransferResponse {
-    const { vendorId } = commandPayload
-    try {
-      if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
-        return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
+    commandPayload: UnlockConnectorRequest
+  ): Promise<UnlockConnectorResponse> {
+    const { connectorId } = commandPayload
+    if (!chargingStation.hasConnector(connectorId)) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId.toString()}`
+      )
+      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
+    }
+    if (connectorId === 0) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`
+      )
+      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
+    }
+    if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
+      const stopResponse = await chargingStation.stopTransactionOnConnector(
+        connectorId,
+        OCPP16StopTransactionReason.UNLOCK_COMMAND
+      )
+      if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+        return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
       }
-      return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
-    } catch (error) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
-        chargingStation,
-        OCPP16IncomingRequestCommand.DATA_TRANSFER,
-        error as Error,
-        { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
-      )!
+      return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
     }
+    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+      chargingStation,
+      connectorId,
+      OCPP16ChargePointStatus.Available
+    )
+    return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
   }
 
-  private async handleRequestReserveNow (
+  private handleRequestUpdateFirmware (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16ReserveNowRequest
-  ): Promise<OCPP16ReserveNowResponse> {
+    commandPayload: OCPP16UpdateFirmwareRequest
+  ): OCPP16UpdateFirmwareResponse {
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
-        OCPP16SupportedFeatureProfiles.Reservation,
-        OCPP16IncomingRequestCommand.RESERVE_NOW
+        OCPP16SupportedFeatureProfiles.FirmwareManagement,
+        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
       )
     ) {
-      return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
+      )
+      return OCPP16Constants.OCPP_RESPONSE_EMPTY
     }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
-    const { reservationId, idTag, connectorId } = commandPayload
-    let response: OCPP16ReserveNowResponse
-    try {
-      if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
-        return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
-      }
-      if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
-        return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
-      }
-      if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
-        return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
-      }
-      await removeExpiredReservations(chargingStation)
-      switch (chargingStation.getConnectorStatus(connectorId)?.status) {
-        case OCPP16ChargePointStatus.Faulted:
-          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
-          break
-        case OCPP16ChargePointStatus.Preparing:
-        case OCPP16ChargePointStatus.Charging:
-        case OCPP16ChargePointStatus.SuspendedEV:
-        case OCPP16ChargePointStatus.SuspendedEVSE:
-        case OCPP16ChargePointStatus.Finishing:
-          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
-          break
-        case OCPP16ChargePointStatus.Unavailable:
-          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
-          break
-        case OCPP16ChargePointStatus.Reserved:
-          if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
-            response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
-            break
-          }
-        // eslint-disable-next-line no-fallthrough
-        default:
-          if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
-            response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
-            break
-          }
-          await chargingStation.addReservation({
-            id: commandPayload.reservationId,
-            ...commandPayload
-          })
-          response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
-          break
-      }
-      return response
-    } catch (error) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
-        chargingStation,
-        OCPP16IncomingRequestCommand.RESERVE_NOW,
-        error as Error,
-        { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
-      )!
+    commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
+    const { retrieveDate } = commandPayload
+    if (
+      chargingStation.stationInfo?.firmwareStatus != null &&
+      chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
+      )
+      return OCPP16Constants.OCPP_RESPONSE_EMPTY
     }
+    const now = Date.now()
+    if (retrieveDate.getTime() <= now) {
+      this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
+    } else {
+      setTimeout(() => {
+        this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
+      }, retrieveDate.getTime() - now)
+    }
+    return OCPP16Constants.OCPP_RESPONSE_EMPTY
   }
 
-  private async handleRequestCancelReservation (
+  private notifyRemoteStartTransactionRejected (
     chargingStation: ChargingStation,
-    commandPayload: OCPP16CancelReservationRequest
-  ): Promise<GenericResponse> {
+    connectorId: number,
+    idTag: string
+  ): GenericResponse {
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    logger.debug(
+      `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        chargingStation.stationInfo?.chargingStationId
+      }#${connectorId.toString()}, idTag '${idTag}', availability '${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        connectorStatus?.availability
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      }', status '${connectorStatus?.status}'`
+    )
+    return OCPP16Constants.OCPP_RESPONSE_REJECTED
+  }
+
+  private setRemoteStartTransactionChargingProfile (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    chargingProfile: OCPP16ChargingProfile
+  ): boolean {
     if (
-      !OCPP16ServiceUtils.checkFeatureProfile(
-        chargingStation,
-        OCPP16SupportedFeatureProfiles.Reservation,
-        OCPP16IncomingRequestCommand.CANCEL_RESERVATION
+      chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
+      chargingProfile.transactionId == null
+    ) {
+      OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
+      logger.debug(
+        `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          chargingStation.stationInfo?.chargingStationId
+        }#${connectorId.toString()}`,
+        chargingProfile
       )
+      return true
+    }
+    logger.debug(
+      `${chargingStation.logPrefix()} Not allowed to set ${
+        chargingProfile.chargingProfilePurpose
+      } charging profile(s)${chargingProfile.transactionId != null ? ' with transactionId set' : ''} at remote start transaction`
+    )
+    return false
+  }
+
+  private async updateFirmwareSimulation (
+    chargingStation: ChargingStation,
+    maxDelay = 30,
+    minDelay = 15
+  ): Promise<void> {
+    if (!checkChargingStationState(chargingStation, chargingStation.logPrefix())) {
+      return
+    }
+    if (chargingStation.hasEvses) {
+      for (const [evseId, evseStatus] of chargingStation.evses) {
+        if (evseId > 0) {
+          for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+            if (connectorStatus.transactionStarted === false) {
+              await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+                chargingStation,
+                connectorId,
+                OCPP16ChargePointStatus.Unavailable
+              )
+            }
+          }
+        }
+      }
+    } else {
+      for (const connectorId of chargingStation.connectors.keys()) {
+        if (
+          connectorId > 0 &&
+          chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
+        ) {
+          await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+            chargingStation,
+            connectorId,
+            OCPP16ChargePointStatus.Unavailable
+          )
+        }
+      }
+    }
+    await chargingStation.ocppRequestService.requestHandler<
+      OCPP16FirmwareStatusNotificationRequest,
+      OCPP16FirmwareStatusNotificationResponse
+    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      status: OCPP16FirmwareStatus.Downloading,
+    })
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
+    if (
+      chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
+      OCPP16FirmwareStatus.DownloadFailed
     ) {
-      return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
+      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
+      await chargingStation.ocppRequestService.requestHandler<
+        OCPP16FirmwareStatusNotificationRequest,
+        OCPP16FirmwareStatusNotificationResponse
+      >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+        status: chargingStation.stationInfo.firmwareUpgrade.failureStatus,
+      })
+      chargingStation.stationInfo.firmwareStatus =
+        chargingStation.stationInfo.firmwareUpgrade.failureStatus
+      return
     }
-    try {
-      const { reservationId } = commandPayload
-      const reservation = chargingStation.getReservationBy('reservationId', reservationId)
-      if (reservation == null) {
+    await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
+    await chargingStation.ocppRequestService.requestHandler<
+      OCPP16FirmwareStatusNotificationRequest,
+      OCPP16FirmwareStatusNotificationResponse
+    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      status: OCPP16FirmwareStatus.Downloaded,
+    })
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
+    let wasTransactionsStarted = false
+    let transactionsStarted: boolean
+    do {
+      const runningTransactions = chargingStation.getNumberOfRunningTransactions()
+      if (runningTransactions > 0) {
+        const waitTime = secondsToMilliseconds(15)
         logger.debug(
-          `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
+          `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions.toString()} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
+            waitTime
+          )} before continuing firmware update simulation`
         )
-        return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
+        await sleep(waitTime)
+        transactionsStarted = true
+        wasTransactionsStarted = true
+      } else {
+        if (chargingStation.hasEvses) {
+          for (const [evseId, evseStatus] of chargingStation.evses) {
+            if (evseId > 0) {
+              for (const [connectorId, connectorStatus] of evseStatus.connectors) {
+                if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
+                  await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+                    chargingStation,
+                    connectorId,
+                    OCPP16ChargePointStatus.Unavailable
+                  )
+                }
+              }
+            }
+          }
+        } else {
+          for (const connectorId of chargingStation.connectors.keys()) {
+            if (
+              connectorId > 0 &&
+              chargingStation.getConnectorStatus(connectorId)?.status !==
+                OCPP16ChargePointStatus.Unavailable
+            ) {
+              await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+                chargingStation,
+                connectorId,
+                OCPP16ChargePointStatus.Unavailable
+              )
+            }
+          }
+        }
+        transactionsStarted = false
       }
-      await chargingStation.removeReservation(
-        reservation,
-        ReservationTerminationReason.RESERVATION_CANCELED
-      )
-      return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
-    } catch (error) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      return this.handleIncomingRequestError<GenericResponse>(
-        chargingStation,
-        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-        error as Error,
-        { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
-      )!
+    } while (transactionsStarted)
+    !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
+    if (!checkChargingStationState(chargingStation, chargingStation.logPrefix())) {
+      return
+    }
+    await chargingStation.ocppRequestService.requestHandler<
+      OCPP16FirmwareStatusNotificationRequest,
+      OCPP16FirmwareStatusNotificationResponse
+    >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      status: OCPP16FirmwareStatus.Installing,
+    })
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
+    if (
+      chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
+      OCPP16FirmwareStatus.InstallationFailed
+    ) {
+      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
+      await chargingStation.ocppRequestService.requestHandler<
+        OCPP16FirmwareStatusNotificationRequest,
+        OCPP16FirmwareStatusNotificationResponse
+      >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+        status: chargingStation.stationInfo.firmwareUpgrade.failureStatus,
+      })
+      chargingStation.stationInfo.firmwareStatus =
+        chargingStation.stationInfo.firmwareUpgrade.failureStatus
+      return
+    }
+    if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
+      await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
+      await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
     }
   }
+
+  private validatePayload (
+    chargingStation: ChargingStation,
+    commandName: OCPP16IncomingRequestCommand,
+    commandPayload: JsonType
+  ): boolean {
+    if (this.payloadValidateFunctions.has(commandName)) {
+      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
+    }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+    )
+    return false
+  }
 }
index b9643469a791831937d9774e4ef0b9fb68abacbf..5c096532df4a5ebc3e8db6a3f20ba4b2e6282b1e 100644 (file)
@@ -3,6 +3,8 @@
 import type { ValidateFunction } from 'ajv'
 
 import type { ChargingStation } from '../../../charging-station/index.js'
+import type { OCPPResponseService } from '../OCPPResponseService.js'
+
 import { OCPPError } from '../../../exception/index.js'
 import {
   ErrorType,
@@ -21,11 +23,10 @@ import {
   type OCPP16StatusNotificationRequest,
   type OCPP16StopTransactionRequest,
   OCPPVersion,
-  type RequestParams
+  type RequestParams,
 } from '../../../types/index.js'
 import { Constants, generateUUID } from '../../../utils/index.js'
 import { OCPPRequestService } from '../OCPPRequestService.js'
-import type { OCPPResponseService } from '../OCPPResponseService.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
@@ -42,128 +43,109 @@ export class OCPP16RequestService extends OCPPRequestService {
     this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
       [
         OCPP16RequestCommand.AUTHORIZE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeRequest>(
-              'assets/json-schemas/ocpp/1.6/Authorize.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeRequest>(
+            'assets/json-schemas/ocpp/1.6/Authorize.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16RequestCommand.BOOT_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationRequest>(
-              'assets/json-schemas/ocpp/1.6/BootNotification.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationRequest>(
+            'assets/json-schemas/ocpp/1.6/BootNotification.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationRequest>(
-              'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.DATA_TRANSFER,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
+            'assets/json-schemas/ocpp/1.6/DataTransfer.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.HEARTBEAT,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatRequest>(
-              'assets/json-schemas/ocpp/1.6/Heartbeat.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationRequest>(
+            'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.METER_VALUES,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesRequest>(
-              'assets/json-schemas/ocpp/1.6/MeterValues.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationRequest>(
+            'assets/json-schemas/ocpp/1.6/FirmwareStatusNotification.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationRequest>(
-              'assets/json-schemas/ocpp/1.6/StatusNotification.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.HEARTBEAT,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatRequest>(
+            'assets/json-schemas/ocpp/1.6/Heartbeat.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.START_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionRequest>(
-              'assets/json-schemas/ocpp/1.6/StartTransaction.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.METER_VALUES,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesRequest>(
+            'assets/json-schemas/ocpp/1.6/MeterValues.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.STOP_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionRequest>(
-              'assets/json-schemas/ocpp/1.6/StopTransaction.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.START_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionRequest>(
+            'assets/json-schemas/ocpp/1.6/StartTransaction.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.DATA_TRANSFER,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
-              'assets/json-schemas/ocpp/1.6/DataTransfer.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationRequest>(
+            'assets/json-schemas/ocpp/1.6/StatusNotification.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationRequest>(
-              'assets/json-schemas/ocpp/1.6/FirmwareStatusNotification.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionRequest>(
+            'assets/json-schemas/ocpp/1.6/StopTransaction.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP16RequestCommand,
@@ -172,7 +154,7 @@ export class OCPP16RequestService extends OCPPRequestService {
   ): Promise<ResponseType> {
     // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
     if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
-      // Post request actions hook
+      // Pre request actions hook
       switch (commandName) {
         case OCPP16RequestCommand.START_TRANSACTION:
           await OCPP16ServiceUtils.sendAndSetConnectorStatus(
@@ -193,12 +175,13 @@ export class OCPP16RequestService extends OCPPRequestService {
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
       ErrorType.NOT_SUPPORTED,
-      `Unsupported OCPP command '${commandName}'`,
+      `Unsupported OCPP command ${commandName}`,
       commandName,
       commandParams
     )
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP16RequestCommand,
@@ -208,18 +191,18 @@ export class OCPP16RequestService extends OCPPRequestService {
     let energyActiveImportRegister: number
     commandParams = commandParams as JsonObject
     switch (commandName) {
+      case OCPP16RequestCommand.AUTHORIZE:
+        return {
+          idTag: Constants.DEFAULT_IDTAG,
+          ...commandParams,
+        } as unknown as Request
       case OCPP16RequestCommand.BOOT_NOTIFICATION:
+      case OCPP16RequestCommand.DATA_TRANSFER:
       case OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION:
       case OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION:
       case OCPP16RequestCommand.METER_VALUES:
       case OCPP16RequestCommand.STATUS_NOTIFICATION:
-      case OCPP16RequestCommand.DATA_TRANSFER:
         return commandParams as unknown as Request
-      case OCPP16RequestCommand.AUTHORIZE:
-        return {
-          idTag: Constants.DEFAULT_IDTAG,
-          ...commandParams
-        } as unknown as Request
       case OCPP16RequestCommand.HEARTBEAT:
         return OCPP16Constants.OCPP_REQUEST_EMPTY as unknown as Request
       case OCPP16RequestCommand.START_TRANSACTION:
@@ -241,9 +224,9 @@ export class OCPP16RequestService extends OCPPRequestService {
               chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved
                 ? 0
                 : (commandParams.connectorId as number)
-            )!.reservationId
+            )!.reservationId,
           }),
-          ...commandParams
+          ...commandParams,
         } as unknown as Request
       case OCPP16RequestCommand.STOP_TRANSACTION:
         chargingStation.stationInfo?.transactionDataMeterValues === true &&
@@ -268,16 +251,16 @@ export class OCPP16RequestService extends OCPPRequestService {
                 connectorId!,
                 energyActiveImportRegister
               )
-            )
+            ),
           }),
-          ...commandParams
+          ...commandParams,
         } as unknown as Request
       default:
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         throw new OCPPError(
           ErrorType.NOT_SUPPORTED,
           // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          `Unsupported OCPP command '${commandName}'`,
+          `Unsupported OCPP command ${commandName}`,
           commandName,
           commandParams
         )
index e6c3b43472c8ef0975f3e855deebf51b7c013919..553e0ae4096f832e876ab59ca5688dc532875f45 100644 (file)
@@ -1,6 +1,7 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
 import type { ValidateFunction } from 'ajv'
+
 import { secondsToMilliseconds } from 'date-fns'
 
 import {
@@ -8,11 +9,12 @@ import {
   type ChargingStation,
   getConfigurationKey,
   hasReservationExpired,
-  resetConnectorStatus
+  resetConnectorStatus,
 } from '../../../charging-station/index.js'
 import { OCPPError } from '../../../exception/index.js'
 import {
   type ChangeConfigurationResponse,
+  ChargingStationEvents,
   ErrorType,
   type GenericResponse,
   type GetConfigurationResponse,
@@ -48,7 +50,7 @@ import {
   ReservationTerminationReason,
   type ResponseHandler,
   type SetChargingProfileResponse,
-  type UnlockConnectorResponse
+  type UnlockConnectorResponse,
 } from '../../../types/index.js'
 import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js'
 import { OCPPResponseService } from '../OCPPResponseService.js'
@@ -58,8 +60,8 @@ const moduleName = 'OCPP16ResponseService'
 
 export class OCPP16ResponseService extends OCPPResponseService {
   public incomingRequestResponsePayloadValidateFunctions: Map<
-  OCPP16IncomingRequestCommand,
-  ValidateFunction<JsonType>
+    OCPP16IncomingRequestCommand,
+    ValidateFunction<JsonType>
   >
 
   protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
@@ -71,373 +73,326 @@ export class OCPP16ResponseService extends OCPPResponseService {
     // }
     super(OCPPVersion.VERSION_16)
     this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
+      [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
       [
         OCPP16RequestCommand.BOOT_NOTIFICATION,
-        this.handleResponseBootNotification.bind(this) as ResponseHandler
+        this.handleResponseBootNotification.bind(this) as ResponseHandler,
       ],
-      [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler],
-      [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
+      [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler],
       [
-        OCPP16RequestCommand.START_TRANSACTION,
-        this.handleResponseStartTransaction.bind(this) as ResponseHandler
+        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+        this.emptyResponseHandler.bind(this) as ResponseHandler,
       ],
+      [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler],
+      [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler],
+      [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler],
       [
-        OCPP16RequestCommand.STOP_TRANSACTION,
-        this.handleResponseStopTransaction.bind(this) as ResponseHandler
+        OCPP16RequestCommand.START_TRANSACTION,
+        this.handleResponseStartTransaction.bind(this) as ResponseHandler,
       ],
       [
         OCPP16RequestCommand.STATUS_NOTIFICATION,
-        this.emptyResponseHandler.bind(this) as ResponseHandler
+        this.emptyResponseHandler.bind(this) as ResponseHandler,
       ],
-      [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler],
       [
-        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-        this.emptyResponseHandler.bind(this) as ResponseHandler
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        this.handleResponseStopTransaction.bind(this) as ResponseHandler,
       ],
-      [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler],
-      [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler]
     ])
     this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
       [
-        OCPP16RequestCommand.BOOT_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
-              'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.AUTHORIZE,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
+            'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.HEARTBEAT,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
-              'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.BOOT_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
+            'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.AUTHORIZE,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
-              'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.DATA_TRANSFER,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
+            'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.START_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
-              'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
+            'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.STOP_TRANSACTION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
-              'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
+            'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
-              'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.HEARTBEAT,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
+            'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16RequestCommand.METER_VALUES,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
-              'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
+            'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
-              'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.START_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
+            'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.DATA_TRANSFER,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
-              'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.STATUS_NOTIFICATION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
+            'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
-              'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        this.ajv.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
+            'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.incomingRequestResponsePayloadValidateFunctions = new Map<
-    OCPP16IncomingRequestCommand,
-    ValidateFunction<JsonType>
+      OCPP16IncomingRequestCommand,
+      ValidateFunction<JsonType>
     >([
       [
-        OCPP16IncomingRequestCommand.RESET,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
-              'assets/json-schemas/ocpp/1.6/ResetResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
+            'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CACHE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
-              'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
+            'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
-              'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
+            'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
-              'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CLEAR_CACHE,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
+            'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
-              'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
+            'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
-              'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.DATA_TRANSFER,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
+            'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
-              'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
+            'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
-              'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
+            'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
-              'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
+            'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
-              'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
+            'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
-              'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
+            'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
-              'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.RESERVE_NOW,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
+            'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
-              'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.RESET,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
+            'assets/json-schemas/ocpp/1.6/ResetResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.DATA_TRANSFER,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
-              'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
+            'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
-              'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
+            'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.RESERVE_NOW,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
-              'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
+            'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
-        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
-              'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
+        this.ajvIncomingRequest.compile(
+          OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
+            'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP16RequestCommand,
     payload: ResType,
     requestPayload: ReqType
   ): Promise<void> {
-    if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
+    if (
+      chargingStation.inAcceptedState() ||
+      ((chargingStation.inUnknownState() || chargingStation.inPendingState()) &&
+        commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) ||
+      (chargingStation.stationInfo?.ocppStrictCompliance === false &&
+        (chargingStation.inUnknownState() || chargingStation.inPendingState()))
+    ) {
       if (
         this.responseHandlers.has(commandName) &&
         OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
@@ -449,7 +404,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
           if (isAsyncFunction(responseHandler)) {
             await responseHandler(chargingStation, payload, requestPayload)
           } else {
-            (
+            ;(
               responseHandler as (
                 chargingStation: ChargingStation,
                 payload: JsonType,
@@ -468,7 +423,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `'${commandName}' is not implemented to handle response PDU ${JSON.stringify(
+          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
             payload,
             undefined,
             2
@@ -491,56 +446,6 @@ export class OCPP16ResponseService extends OCPPResponseService {
     }
   }
 
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP16RequestCommand,
-    payload: JsonType
-  ): boolean {
-    if (this.payloadValidateFunctions.has(commandName)) {
-      return this.validateResponsePayload(chargingStation, commandName, payload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
-
-  private handleResponseBootNotification (
-    chargingStation: ChargingStation,
-    payload: OCPP16BootNotificationResponse
-  ): void {
-    if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
-      addConfigurationKey(
-        chargingStation,
-        OCPP16StandardParametersKey.HeartbeatInterval,
-        payload.interval.toString(),
-        {},
-        { overwrite: true, save: true }
-      )
-      addConfigurationKey(
-        chargingStation,
-        OCPP16StandardParametersKey.HeartBeatInterval,
-        payload.interval.toString(),
-        { visible: false },
-        { overwrite: true, save: true }
-      )
-      OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
-    }
-    if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
-      const logMsg = `${chargingStation.logPrefix()} Charging station in '${
-        payload.status
-      }' state on the central server`
-      payload.status === RegistrationStatusEnumType.REJECTED
-        ? logger.warn(logMsg)
-        : logger.info(logMsg)
-    } else {
-      logger.error(
-        `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
-        payload
-      )
-    }
-  }
-
   private handleResponseAuthorize (
     chargingStation: ChargingStation,
     payload: OCPP16AuthorizeResponse,
@@ -577,15 +482,15 @@ export class OCPP16ResponseService extends OCPPResponseService {
         logger.debug(
           `${chargingStation.logPrefix()} idTag '${
             requestPayload.idTag
-          }' accepted on connector id ${authorizeConnectorId}`
+          }' accepted on connector id ${authorizeConnectorId.toString()}`
         )
       } else {
         authorizeConnectorStatus.idTagAuthorized = false
         delete authorizeConnectorStatus.authorizeIdTag
         logger.debug(
-          `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
-            payload.idTagInfo.status
-          }'`
+          `${chargingStation.logPrefix()} idTag '${
+            requestPayload.idTag
+          }' rejected with status '${payload.idTagInfo.status}'`
         )
       }
     } else {
@@ -597,6 +502,51 @@ export class OCPP16ResponseService extends OCPPResponseService {
     }
   }
 
+  private handleResponseBootNotification (
+    chargingStation: ChargingStation,
+    payload: OCPP16BootNotificationResponse
+  ): void {
+    if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
+      chargingStation.bootNotificationResponse = payload
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      if (payload.interval != null) {
+        addConfigurationKey(
+          chargingStation,
+          OCPP16StandardParametersKey.HeartbeatInterval,
+          payload.interval.toString(),
+          {},
+          { overwrite: true, save: true }
+        )
+        addConfigurationKey(
+          chargingStation,
+          OCPP16StandardParametersKey.HeartBeatInterval,
+          payload.interval.toString(),
+          { visible: false },
+          { overwrite: true, save: true }
+        )
+      }
+      if (chargingStation.inAcceptedState()) {
+        chargingStation.emit(ChargingStationEvents.accepted)
+      } else if (chargingStation.inPendingState()) {
+        chargingStation.emit(ChargingStationEvents.pending)
+      } else if (chargingStation.inRejectedState()) {
+        chargingStation.emit(ChargingStationEvents.rejected)
+      }
+      const logMsg = `${chargingStation.logPrefix()} Charging station in '${
+        payload.status
+      }' state on the central server`
+      payload.status === RegistrationStatusEnumType.REJECTED
+        ? logger.warn(logMsg)
+        : logger.info(logMsg)
+    } else {
+      delete chargingStation.bootNotificationResponse
+      logger.error(
+        `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
+        payload
+      )
+    }
+  }
+
   private async handleResponseStartTransaction (
     chargingStation: ChargingStation,
     payload: OCPP16StartTransactionResponse,
@@ -605,7 +555,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     const { connectorId } = requestPayload
     if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
+        `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId.toString()}`
       )
       return
     }
@@ -619,8 +569,9 @@ export class OCPP16ResponseService extends OCPPResponseService {
     ) {
       logger.error(
         `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
           connectorStatus.localAuthorizeIdTag
-        } on connector id ${connectorId}`
+        } on connector id ${connectorId.toString()}`
       )
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
       return
@@ -634,14 +585,16 @@ export class OCPP16ResponseService extends OCPPResponseService {
     ) {
       logger.error(
         `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
           connectorStatus.authorizeIdTag
-        } on connector id ${connectorId}`
+        } on connector id ${connectorId.toString()}`
       )
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
       return
     }
     if (
       connectorStatus?.idTagAuthorized === true &&
+      connectorStatus.authorizeIdTag != null &&
       connectorStatus.authorizeIdTag !== requestPayload.idTag
     ) {
       logger.error(
@@ -649,13 +602,14 @@ export class OCPP16ResponseService extends OCPPResponseService {
           requestPayload.idTag
         } different from the authorize request one ${
           connectorStatus.authorizeIdTag
-        } on connector id ${connectorId}`
+        } on connector id ${connectorId.toString()}`
       )
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
       return
     }
     if (
       connectorStatus?.idTagLocalAuthorized === true &&
+      connectorStatus.localAuthorizeIdTag != null &&
       connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
     ) {
       logger.error(
@@ -663,14 +617,15 @@ export class OCPP16ResponseService extends OCPPResponseService {
           requestPayload.idTag
         } different from the local authorized one ${
           connectorStatus.localAuthorizeIdTag
-        } on connector id ${connectorId}`
+        } on connector id ${connectorId.toString()}`
       )
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
       return
     }
     if (connectorStatus?.transactionStarted === true) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
+        `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId.toString()} by idTag ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
           connectorStatus.transactionIdTag
         }`
       )
@@ -682,7 +637,8 @@ export class OCPP16ResponseService extends OCPPResponseService {
           for (const [id, status] of evseStatus.connectors) {
             if (id !== connectorId && status.transactionStarted === true) {
               logger.error(
-                `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
+                `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId.toString()} by connector id ${id.toString()} with idTag ${
+                  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                   status.transactionIdTag
                 }`
               )
@@ -698,15 +654,16 @@ export class OCPP16ResponseService extends OCPPResponseService {
       connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
     ) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
+        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId.toString()} with status ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          connectorStatus?.status
+        }`
       )
       return
     }
     if (!Number.isSafeInteger(payload.transactionId)) {
       logger.warn(
-        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
-          payload.transactionId
-        }, converting to integer`
+        `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId.toString()} with a non integer transaction id ${payload.transactionId.toString()}, converting to integer`
       )
       payload.transactionId = convertToInt(payload.transactionId)
     }
@@ -731,20 +688,14 @@ export class OCPP16ResponseService extends OCPPResponseService {
         if (reservation != null) {
           if (reservation.idTag !== requestPayload.idTag) {
             logger.warn(
-              `${chargingStation.logPrefix()} Reserved transaction ${
-                payload.transactionId
-              } started with a different idTag ${requestPayload.idTag} than the reservation one ${
-                reservation.idTag
-              }`
+              `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with a different idTag ${
+                requestPayload.idTag
+              } than the reservation one ${reservation.idTag}`
             )
           }
           if (hasReservationExpired(reservation)) {
             logger.warn(
-              `${chargingStation.logPrefix()} Reserved transaction ${
-                payload.transactionId
-              } started with expired reservation ${
-                requestPayload.reservationId
-              } (expiry date: ${reservation.expiryDate.toISOString()}))`
+              `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with expired reservation ${requestPayload.reservationId.toString()} (expiry date: ${reservation.expiryDate.toISOString()}))`
             )
           }
           await chargingStation.removeReservation(
@@ -753,20 +704,18 @@ export class OCPP16ResponseService extends OCPPResponseService {
           )
         } else {
           logger.warn(
-            `${chargingStation.logPrefix()} Reserved transaction ${
-              payload.transactionId
-            } started with unknown reservation ${requestPayload.reservationId}`
+            `${chargingStation.logPrefix()} Reserved transaction ${payload.transactionId.toString()} started with unknown reservation ${requestPayload.reservationId.toString()}`
           )
         }
       }
       chargingStation.stationInfo?.beginEndMeterValues === true &&
         (await chargingStation.ocppRequestService.requestHandler<
-        OCPP16MeterValuesRequest,
-        OCPP16MeterValuesResponse
+          OCPP16MeterValuesRequest,
+          OCPP16MeterValuesResponse
         >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
           connectorId,
+          meterValue: [connectorStatus.transactionBeginMeterValue],
           transactionId: payload.transactionId,
-          meterValue: [connectorStatus.transactionBeginMeterValue]
         } satisfies OCPP16MeterValuesRequest))
       await OCPP16ServiceUtils.sendAndSetConnectorStatus(
         chargingStation,
@@ -774,11 +723,10 @@ export class OCPP16ResponseService extends OCPPResponseService {
         OCPP16ChargePointStatus.Charging
       )
       logger.info(
-        `${chargingStation.logPrefix()} Transaction with id ${
-          payload.transactionId
-        } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
-          requestPayload.idTag
-        }'`
+        `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId.toString()} STARTED on ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          chargingStation.stationInfo?.chargingStationId
+        }#${connectorId.toString()} for idTag '${requestPayload.idTag}'`
       )
       if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -796,15 +744,15 @@ export class OCPP16ResponseService extends OCPPResponseService {
       )
     } else {
       logger.warn(
-        `${chargingStation.logPrefix()} Starting transaction with id ${
-          payload.transactionId
-        } REJECTED on ${
+        `${chargingStation.logPrefix()} Starting transaction with id ${payload.transactionId.toString()} REJECTED on ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
           chargingStation.stationInfo?.chargingStationId
-        }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
+        }#${connectorId.toString()} with status '${payload.idTagInfo.status}', idTag '${
           requestPayload.idTag
         }'${
           OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
-            ? `, reservationId '${requestPayload.reservationId}'`
+            ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `, reservationId '${requestPayload.reservationId?.toString()}'`
             : ''
         }`
       )
@@ -812,16 +760,6 @@ export class OCPP16ResponseService extends OCPPResponseService {
     }
   }
 
-  private async resetConnectorOnStartTransactionError (
-    chargingStation: ChargingStation,
-    connectorId: number
-  ): Promise<void> {
-    chargingStation.stopMeterValues(connectorId)
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    resetConnectorStatus(connectorStatus)
-    await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
-  }
-
   private async handleResponseStopTransaction (
     chargingStation: ChargingStation,
     payload: OCPP16StopTransactionResponse,
@@ -832,9 +770,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
     )
     if (transactionConnectorId == null) {
       logger.error(
-        `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
-          requestPayload.transactionId
-        }`
+        `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${requestPayload.transactionId.toString()}`
       )
       return
     }
@@ -842,18 +778,18 @@ export class OCPP16ResponseService extends OCPPResponseService {
       chargingStation.stationInfo.ocppStrictCompliance === false &&
       chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
       (await chargingStation.ocppRequestService.requestHandler<
-      OCPP16MeterValuesRequest,
-      OCPP16MeterValuesResponse
+        OCPP16MeterValuesRequest,
+        OCPP16MeterValuesResponse
       >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
         connectorId: transactionConnectorId,
-        transactionId: requestPayload.transactionId,
         meterValue: [
           OCPP16ServiceUtils.buildTransactionEndMeterValue(
             chargingStation,
             transactionConnectorId,
             requestPayload.meterStop
-          )
-        ]
+          ),
+        ],
+        transactionId: requestPayload.transactionId,
       }))
     if (
       !chargingStation.isChargingStationAvailable() ||
@@ -877,11 +813,11 @@ export class OCPP16ResponseService extends OCPPResponseService {
     }
     resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
     chargingStation.stopMeterValues(transactionConnectorId)
-    const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
-      requestPayload.transactionId
-    } STOPPED on ${
+    const logMsg = `${chargingStation.logPrefix()} Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       chargingStation.stationInfo?.chargingStationId
-    }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+    }#${transactionConnectorId.toString()} with status '${payload.idTagInfo?.status}'`
     if (
       payload.idTagInfo == null ||
       payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
@@ -891,4 +827,28 @@ export class OCPP16ResponseService extends OCPPResponseService {
       logger.warn(logMsg)
     }
   }
+
+  private async resetConnectorOnStartTransactionError (
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): Promise<void> {
+    chargingStation.stopMeterValues(connectorId)
+    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+    resetConnectorStatus(connectorStatus)
+    await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
+  }
+
+  private validatePayload (
+    chargingStation: ChargingStation,
+    commandName: OCPP16RequestCommand,
+    payload: JsonType
+  ): boolean {
+    if (this.payloadValidateFunctions.has(commandName)) {
+      return this.validateResponsePayload(chargingStation, commandName, payload)
+    }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+    )
+    return false
+  }
 }
index d9144064003fb5bf07e0443720e17ac2af084501..dba9fbf964923b23779bd141d10ebf1be9adcc4e 100644 (file)
@@ -1,6 +1,7 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
 import type { JSONSchemaType } from 'ajv'
+
 import {
   addSeconds,
   areIntervalsOverlapping,
@@ -8,13 +9,13 @@ import {
   type Interval,
   isAfter,
   isBefore,
-  isWithinInterval
+  isWithinInterval,
 } from 'date-fns'
 
 import {
   type ChargingStation,
   hasFeatureProfile,
-  hasReservationExpired
+  hasReservationExpired,
 } from '../../../charging-station/index.js'
 import {
   type ConfigurationKey,
@@ -35,37 +36,21 @@ import {
   OCPP16StandardParametersKey,
   OCPP16StopTransactionReason,
   type OCPP16SupportedFeatureProfiles,
-  OCPPVersion
+  OCPPVersion,
 } from '../../../types/index.js'
 import { convertToDate, isNotEmptyArray, logger, roundTo } from '../../../utils/index.js'
 import { OCPPServiceUtils } from '../OCPPServiceUtils.js'
 import { OCPP16Constants } from './OCPP16Constants.js'
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
-  public static checkFeatureProfile (
-    chargingStation: ChargingStation,
-    featureProfile: OCPP16SupportedFeatureProfiles,
-    command: OCPP16RequestCommand | OCPP16IncomingRequestCommand
-  ): boolean {
-    if (hasFeatureProfile(chargingStation, featureProfile) === false) {
-      logger.warn(
-        `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
-          OCPP16StandardParametersKey.SupportedFeatureProfiles
-        } in configuration`
-      )
-      return false
-    }
-    return true
-  }
-
   public static buildTransactionBeginMeterValue (
     chargingStation: ChargingStation,
     connectorId: number,
     meterStart: number | undefined
   ): OCPP16MeterValue {
     const meterValue: OCPP16MeterValue = {
+      sampledValue: [],
       timestamp: new Date(),
-      sampledValue: []
     }
     // Energy.Active.Import.Register measurand (default)
     const sampledValueTemplate = OCPP16ServiceUtils.getSampledValueTemplate(
@@ -95,25 +80,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return meterValues
   }
 
-  public static remoteStopTransaction = async (
-    chargingStation: ChargingStation,
-    connectorId: number
-  ): Promise<GenericResponse> => {
-    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-      chargingStation,
-      connectorId,
-      OCPP16ChargePointStatus.Finishing
-    )
-    const stopResponse = await chargingStation.stopTransactionOnConnector(
-      connectorId,
-      OCPP16StopTransactionReason.REMOTE
-    )
-    if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
-      return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
-    }
-    return OCPP16Constants.OCPP_RESPONSE_REJECTED
-  }
-
   public static changeAvailability = async (
     chargingStation: ChargingStation,
     connectorIds: number[],
@@ -145,46 +111,20 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
   }
 
-  public static setChargingProfile (
+  public static checkFeatureProfile (
     chargingStation: ChargingStation,
-    connectorId: number,
-    cp: OCPP16ChargingProfile
-  ): void {
-    if (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles == null) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
-      )
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
-    }
-    if (!Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
-      logger.error(
-        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`
+    featureProfile: OCPP16SupportedFeatureProfiles,
+    command: OCPP16IncomingRequestCommand | OCPP16RequestCommand
+  ): boolean {
+    if (hasFeatureProfile(chargingStation, featureProfile) === false) {
+      logger.warn(
+        `${chargingStation.logPrefix()} Trying to '${command}' without '${featureProfile}' feature enabled in ${
+          OCPP16StandardParametersKey.SupportedFeatureProfiles
+        } in configuration`
       )
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
-    }
-    cp.chargingSchedule.startSchedule = convertToDate(cp.chargingSchedule.startSchedule)
-    cp.validFrom = convertToDate(cp.validFrom)
-    cp.validTo = convertToDate(cp.validTo)
-    let cpReplaced = false
-    if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      for (const [index, chargingProfile] of chargingStation
-        .getConnectorStatus(connectorId)!
-        .chargingProfiles!.entries()) {
-        if (
-          chargingProfile.chargingProfileId === cp.chargingProfileId ||
-          (chargingProfile.stackLevel === cp.stackLevel &&
-            chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
-        ) {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp
-          cpReplaced = true
-        }
-      }
+      return false
     }
-    !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
+    return true
   }
 
   public static clearChargingProfiles = (
@@ -192,7 +132,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     commandPayload: OCPP16ClearChargingProfileRequest,
     chargingProfiles: OCPP16ChargingProfile[] | undefined
   ): boolean => {
-    const { id, chargingProfilePurpose, stackLevel } = commandPayload
+    const { chargingProfilePurpose, id, stackLevel } = commandPayload
     let clearedCP = false
     if (isNotEmptyArray(chargingProfiles)) {
       chargingProfiles.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
@@ -249,24 +189,24 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       OCPP16ServiceUtils.composeChargingSchedule(chargingScheduleLower!, compositeInterval)
     const compositeChargingScheduleHigherInterval: Interval = {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      start: compositeChargingScheduleHigher!.startSchedule!,
       end: addSeconds(
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         compositeChargingScheduleHigher!.startSchedule!,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         compositeChargingScheduleHigher!.duration!
-      )
+      ),
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: compositeChargingScheduleHigher!.startSchedule!,
     }
     const compositeChargingScheduleLowerInterval: Interval = {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      start: compositeChargingScheduleLower!.startSchedule!,
       end: addSeconds(
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         compositeChargingScheduleLower!.startSchedule!,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         compositeChargingScheduleLower!.duration!
-      )
+      ),
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      start: compositeChargingScheduleLower!.startSchedule!,
     }
     const higherFirst = isBefore(
       compositeChargingScheduleHigherInterval.start,
@@ -282,18 +222,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         ...compositeChargingScheduleLower,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         ...compositeChargingScheduleHigher!,
-        startSchedule: higherFirst
-          ? (compositeChargingScheduleHigherInterval.start as Date)
-          : (compositeChargingScheduleLowerInterval.start as Date),
-        duration: higherFirst
-          ? differenceInSeconds(
-            compositeChargingScheduleLowerInterval.end,
-            compositeChargingScheduleHigherInterval.start
-          )
-          : differenceInSeconds(
-            compositeChargingScheduleHigherInterval.end,
-            compositeChargingScheduleLowerInterval.start
-          ),
         chargingSchedulePeriod: [
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
           ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
@@ -305,7 +233,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   differenceInSeconds(
                     compositeChargingScheduleHigherInterval.start,
                     compositeChargingScheduleLowerInterval.start
-                  )
+                  ),
             }
           }),
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -318,28 +246,28 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                     compositeChargingScheduleLowerInterval.start,
                     compositeChargingScheduleHigherInterval.start
                   )
-                : 0
+                : 0,
             }
-          })
-        ].sort((a, b) => a.startPeriod - b.startPeriod)
+          }),
+        ].sort((a, b) => a.startPeriod - b.startPeriod),
+        duration: higherFirst
+          ? differenceInSeconds(
+            compositeChargingScheduleLowerInterval.end,
+            compositeChargingScheduleHigherInterval.start
+          )
+          : differenceInSeconds(
+            compositeChargingScheduleHigherInterval.end,
+            compositeChargingScheduleLowerInterval.start
+          ),
+        startSchedule: higherFirst
+          ? (compositeChargingScheduleHigherInterval.start as Date)
+          : (compositeChargingScheduleLowerInterval.start as Date),
       }
     }
     return {
       ...compositeChargingScheduleLower,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       ...compositeChargingScheduleHigher!,
-      startSchedule: higherFirst
-        ? (compositeChargingScheduleHigherInterval.start as Date)
-        : (compositeChargingScheduleLowerInterval.start as Date),
-      duration: higherFirst
-        ? differenceInSeconds(
-          compositeChargingScheduleLowerInterval.end,
-          compositeChargingScheduleHigherInterval.start
-        )
-        : differenceInSeconds(
-          compositeChargingScheduleHigherInterval.end,
-          compositeChargingScheduleLowerInterval.start
-        ),
       chargingSchedulePeriod: [
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         ...compositeChargingScheduleHigher!.chargingSchedulePeriod.map(schedulePeriod => {
@@ -351,7 +279,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 differenceInSeconds(
                   compositeChargingScheduleHigherInterval.start,
                   compositeChargingScheduleLowerInterval.start
-                )
+                ),
           }
         }),
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -365,8 +293,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   schedulePeriod.startPeriod
                 ),
                 {
+                  end: compositeChargingScheduleHigherInterval.end,
                   start: compositeChargingScheduleLowerInterval.start,
-                  end: compositeChargingScheduleHigherInterval.end
                 }
               )
             ) {
@@ -382,8 +310,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   schedulePeriod.startPeriod
                 ),
                 {
+                  end: compositeChargingScheduleHigherInterval.end,
                   start: compositeChargingScheduleLowerInterval.start,
-                  end: compositeChargingScheduleHigherInterval.end
                 }
               ) &&
               isWithinInterval(
@@ -393,8 +321,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   compositeChargingScheduleLower!.chargingSchedulePeriod[index + 1].startPeriod
                 ),
                 {
+                  end: compositeChargingScheduleHigherInterval.end,
                   start: compositeChargingScheduleLowerInterval.start,
-                  end: compositeChargingScheduleHigherInterval.end
                 }
               )
             ) {
@@ -408,8 +336,8 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                   schedulePeriod.startPeriod
                 ),
                 {
+                  end: compositeChargingScheduleLowerInterval.end,
                   start: compositeChargingScheduleHigherInterval.start,
-                  end: compositeChargingScheduleLowerInterval.end
                 }
               )
             ) {
@@ -429,20 +357,25 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                     compositeChargingScheduleLowerInterval.start,
                     compositeChargingScheduleHigherInterval.start
                   )
-                : 0
+                : 0,
             }
-          })
-      ].sort((a, b) => a.startPeriod - b.startPeriod)
+          }),
+      ].sort((a, b) => a.startPeriod - b.startPeriod),
+      duration: higherFirst
+        ? differenceInSeconds(
+          compositeChargingScheduleLowerInterval.end,
+          compositeChargingScheduleHigherInterval.start
+        )
+        : differenceInSeconds(
+          compositeChargingScheduleHigherInterval.end,
+          compositeChargingScheduleLowerInterval.start
+        ),
+      startSchedule: higherFirst
+        ? (compositeChargingScheduleHigherInterval.start as Date)
+        : (compositeChargingScheduleLowerInterval.start as Date),
     }
   }
 
-  public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
-    if (key.visible == null) {
-      return true
-    }
-    return key.visible
-  }
-
   public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -462,7 +395,7 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
         chargingStationReservation.idTag === idTag)
     ) {
       logger.debug(
-        `${chargingStation.logPrefix()} Connector id ${connectorId} has a valid reservation for idTag ${idTag}: %j`,
+        `${chargingStation.logPrefix()} Connector id ${connectorId.toString()} has a valid reservation for idTag ${idTag}: %j`,
         connectorReservation ?? chargingStationReservation
       )
       return true
@@ -470,12 +403,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     return false
   }
 
-  public static parseJsonSchemaFile<T extends JsonType>(
+  public static isConfigurationKeyVisible (key: ConfigurationKey): boolean {
+    if (key.visible == null) {
+      return true
+    }
+    return key.visible
+  }
+
+  public static override parseJsonSchemaFile<T extends JsonType>(
     relativePath: string,
     moduleName?: string,
     methodName?: string
   ): JSONSchemaType<T> {
-    return super.parseJsonSchemaFile<T>(
+    return OCPPServiceUtils.parseJsonSchemaFile<T>(
       relativePath,
       OCPPVersion.VERSION_16,
       moduleName,
@@ -483,26 +423,82 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     )
   }
 
+  public static remoteStopTransaction = async (
+    chargingStation: ChargingStation,
+    connectorId: number
+  ): Promise<GenericResponse> => {
+    await OCPP16ServiceUtils.sendAndSetConnectorStatus(
+      chargingStation,
+      connectorId,
+      OCPP16ChargePointStatus.Finishing
+    )
+    const stopResponse = await chargingStation.stopTransactionOnConnector(
+      connectorId,
+      OCPP16StopTransactionReason.REMOTE
+    )
+    if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+      return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
+    }
+    return OCPP16Constants.OCPP_RESPONSE_REJECTED
+  }
+
+  public static setChargingProfile (
+    chargingStation: ChargingStation,
+    connectorId: number,
+    cp: OCPP16ChargingProfile
+  ): void {
+    if (chargingStation.getConnectorStatus(connectorId)?.chargingProfiles == null) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId.toString()} with an uninitialized charging profiles array attribute, applying deferred initialization`
+      )
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
+    }
+    if (!Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+      logger.error(
+        `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId.toString()} with an improper attribute type for the charging profiles array, applying proper type deferred initialization`
+      )
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles = []
+    }
+    cp.chargingSchedule.startSchedule = convertToDate(cp.chargingSchedule.startSchedule)
+    cp.validFrom = convertToDate(cp.validFrom)
+    cp.validTo = convertToDate(cp.validTo)
+    let cpReplaced = false
+    if (isNotEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      for (const [index, chargingProfile] of chargingStation
+        .getConnectorStatus(connectorId)!
+        .chargingProfiles!.entries()) {
+        if (
+          chargingProfile.chargingProfileId === cp.chargingProfileId ||
+          (chargingProfile.stackLevel === cp.stackLevel &&
+            chargingProfile.chargingProfilePurpose === cp.chargingProfilePurpose)
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          chargingStation.getConnectorStatus(connectorId)!.chargingProfiles![index] = cp
+          cpReplaced = true
+        }
+      }
+    }
+    !cpReplaced && chargingStation.getConnectorStatus(connectorId)?.chargingProfiles?.push(cp)
+  }
+
   private static readonly composeChargingSchedule = (
     chargingSchedule: OCPP16ChargingSchedule,
     compositeInterval: Interval
   ): OCPP16ChargingSchedule | undefined => {
     const chargingScheduleInterval: Interval = {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      start: chargingSchedule.startSchedule!,
+      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!),
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!)
+      start: chargingSchedule.startSchedule!,
     }
     if (areIntervalsOverlapping(chargingScheduleInterval, compositeInterval)) {
       chargingSchedule.chargingSchedulePeriod.sort((a, b) => a.startPeriod - b.startPeriod)
       if (isBefore(chargingScheduleInterval.start, compositeInterval.start)) {
         return {
           ...chargingSchedule,
-          startSchedule: compositeInterval.start as Date,
-          duration: differenceInSeconds(
-            chargingScheduleInterval.end,
-            compositeInterval.start as Date
-          ),
           chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod
             .filter((schedulePeriod, index) => {
               if (
@@ -536,22 +532,27 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
                 schedulePeriod.startPeriod = 0
               }
               return schedulePeriod
-            })
+            }),
+          duration: differenceInSeconds(
+            chargingScheduleInterval.end,
+            compositeInterval.start as Date
+          ),
+          startSchedule: compositeInterval.start as Date,
         }
       }
       if (isAfter(chargingScheduleInterval.end, compositeInterval.end)) {
         return {
           ...chargingSchedule,
-          duration: differenceInSeconds(
-            compositeInterval.end as Date,
-            chargingScheduleInterval.start
-          ),
           chargingSchedulePeriod: chargingSchedule.chargingSchedulePeriod.filter(schedulePeriod =>
             isWithinInterval(
               addSeconds(chargingScheduleInterval.start, schedulePeriod.startPeriod),
               compositeInterval
             )
-          )
+          ),
+          duration: differenceInSeconds(
+            compositeInterval.end as Date,
+            chargingScheduleInterval.start
+          ),
         }
       }
       return chargingSchedule
index 7acf7aef03ba32df4c3631a91d7ffff164b556ac..c94fbe9f10d486e2eeb6a98b52bf25ad8a7e70d7 100644 (file)
@@ -1,23 +1,26 @@
 import {
   type ConnectorStatusTransition,
-  OCPP20ConnectorStatusEnumType
+  OCPP20ConnectorStatusEnumType,
 } from '../../../types/index.js'
 import { OCPPConstants } from '../OCPPConstants.js'
 
 export class OCPP20Constants extends OCPPConstants {
-  static readonly ChargingStationStatusTransitions: Readonly<ConnectorStatusTransition[]> =
+  static readonly ChargingStationStatusTransitions: readonly ConnectorStatusTransition[] =
     Object.freeze([
       { to: OCPP20ConnectorStatusEnumType.Available },
       // { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Available },
       {
         from: OCPP20ConnectorStatusEnumType.Available,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
+        to: OCPP20ConnectorStatusEnumType.Unavailable,
+      },
+      {
+        from: OCPP20ConnectorStatusEnumType.Available,
+        to: OCPP20ConnectorStatusEnumType.Faulted,
       },
-      { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Faulted },
       { to: OCPP20ConnectorStatusEnumType.Unavailable },
       {
         from: OCPP20ConnectorStatusEnumType.Unavailable,
-        to: OCPP20ConnectorStatusEnumType.Available
+        to: OCPP20ConnectorStatusEnumType.Available,
       },
       // {
       //   from: OCPP20ConnectorStatusEnumType.Unavailable,
@@ -25,70 +28,104 @@ export class OCPP20Constants extends OCPPConstants {
       // },
       {
         from: OCPP20ConnectorStatusEnumType.Unavailable,
-        to: OCPP20ConnectorStatusEnumType.Faulted
+        to: OCPP20ConnectorStatusEnumType.Faulted,
       },
       { to: OCPP20ConnectorStatusEnumType.Faulted },
-      { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Available },
       {
         from: OCPP20ConnectorStatusEnumType.Faulted,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
-      }
-      // { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Faulted }
-    ])
-
-  static readonly ConnectorStatusTransitions: Readonly<ConnectorStatusTransition[]> = Object.freeze(
-    [
-      { to: OCPP20ConnectorStatusEnumType.Available },
-      // { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Available },
-      { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Occupied },
-      { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Reserved },
-      {
-        from: OCPP20ConnectorStatusEnumType.Available,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
-      },
-      { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Faulted },
-      // { to: OCPP20ConnectorStatusEnumType.Occupied },
-      { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Available },
-      // { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Occupied },
-      // { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Reserved },
-      {
-        from: OCPP20ConnectorStatusEnumType.Occupied,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
-      },
-      { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Faulted },
-      // { to: OCPP20ConnectorStatusEnumType.Reserved },
-      { from: OCPP20ConnectorStatusEnumType.Reserved, to: OCPP20ConnectorStatusEnumType.Available },
-      { from: OCPP20ConnectorStatusEnumType.Reserved, to: OCPP20ConnectorStatusEnumType.Occupied },
-      // { from: OCPP20ConnectorStatusEnumType.Reserved, to: OCPP20ConnectorStatusEnumType.Reserved },
-      {
-        from: OCPP20ConnectorStatusEnumType.Reserved,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
+        to: OCPP20ConnectorStatusEnumType.Available,
       },
-      { from: OCPP20ConnectorStatusEnumType.Reserved, to: OCPP20ConnectorStatusEnumType.Faulted },
-      { to: OCPP20ConnectorStatusEnumType.Unavailable },
-      {
-        from: OCPP20ConnectorStatusEnumType.Unavailable,
-        to: OCPP20ConnectorStatusEnumType.Available
-      },
-      {
-        from: OCPP20ConnectorStatusEnumType.Unavailable,
-        to: OCPP20ConnectorStatusEnumType.Occupied
-      },
-      // { from: OCPP20ConnectorStatusEnumType.Unavailable, to: OCPP20ConnectorStatusEnumType.Reserved },
-      // { from: OCPP20ConnectorStatusEnumType.Unavailable, to: OCPP20ConnectorStatusEnumType.Unavailable },
-      {
-        from: OCPP20ConnectorStatusEnumType.Unavailable,
-        to: OCPP20ConnectorStatusEnumType.Faulted
-      },
-      { to: OCPP20ConnectorStatusEnumType.Faulted },
-      { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Available },
-      { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Occupied },
-      { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Reserved },
       {
         from: OCPP20ConnectorStatusEnumType.Faulted,
-        to: OCPP20ConnectorStatusEnumType.Unavailable
-      }
+        to: OCPP20ConnectorStatusEnumType.Unavailable,
+      },
       // { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Faulted }
-    ]
-  )
+    ])
+
+  static readonly ConnectorStatusTransitions: readonly ConnectorStatusTransition[] = Object.freeze([
+    { to: OCPP20ConnectorStatusEnumType.Available },
+    // { from: OCPP20ConnectorStatusEnumType.Available, to: OCPP20ConnectorStatusEnumType.Available },
+    {
+      from: OCPP20ConnectorStatusEnumType.Available,
+      to: OCPP20ConnectorStatusEnumType.Occupied,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Available,
+      to: OCPP20ConnectorStatusEnumType.Reserved,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Available,
+      to: OCPP20ConnectorStatusEnumType.Unavailable,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Available,
+      to: OCPP20ConnectorStatusEnumType.Faulted,
+    },
+    // { to: OCPP20ConnectorStatusEnumType.Occupied },
+    {
+      from: OCPP20ConnectorStatusEnumType.Occupied,
+      to: OCPP20ConnectorStatusEnumType.Available,
+    },
+    // { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Occupied },
+    // { from: OCPP20ConnectorStatusEnumType.Occupied, to: OCPP20ConnectorStatusEnumType.Reserved },
+    {
+      from: OCPP20ConnectorStatusEnumType.Occupied,
+      to: OCPP20ConnectorStatusEnumType.Unavailable,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Occupied,
+      to: OCPP20ConnectorStatusEnumType.Faulted,
+    },
+    // { to: OCPP20ConnectorStatusEnumType.Reserved },
+    {
+      from: OCPP20ConnectorStatusEnumType.Reserved,
+      to: OCPP20ConnectorStatusEnumType.Available,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Reserved,
+      to: OCPP20ConnectorStatusEnumType.Occupied,
+    },
+    // { from: OCPP20ConnectorStatusEnumType.Reserved, to: OCPP20ConnectorStatusEnumType.Reserved },
+    {
+      from: OCPP20ConnectorStatusEnumType.Reserved,
+      to: OCPP20ConnectorStatusEnumType.Unavailable,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Reserved,
+      to: OCPP20ConnectorStatusEnumType.Faulted,
+    },
+    { to: OCPP20ConnectorStatusEnumType.Unavailable },
+    {
+      from: OCPP20ConnectorStatusEnumType.Unavailable,
+      to: OCPP20ConnectorStatusEnumType.Available,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Unavailable,
+      to: OCPP20ConnectorStatusEnumType.Occupied,
+    },
+    // { from: OCPP20ConnectorStatusEnumType.Unavailable, to: OCPP20ConnectorStatusEnumType.Reserved },
+    // { from: OCPP20ConnectorStatusEnumType.Unavailable, to: OCPP20ConnectorStatusEnumType.Unavailable },
+    {
+      from: OCPP20ConnectorStatusEnumType.Unavailable,
+      to: OCPP20ConnectorStatusEnumType.Faulted,
+    },
+    { to: OCPP20ConnectorStatusEnumType.Faulted },
+    {
+      from: OCPP20ConnectorStatusEnumType.Faulted,
+      to: OCPP20ConnectorStatusEnumType.Available,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Faulted,
+      to: OCPP20ConnectorStatusEnumType.Occupied,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Faulted,
+      to: OCPP20ConnectorStatusEnumType.Reserved,
+    },
+    {
+      from: OCPP20ConnectorStatusEnumType.Faulted,
+      to: OCPP20ConnectorStatusEnumType.Unavailable,
+    },
+    // { from: OCPP20ConnectorStatusEnumType.Faulted, to: OCPP20ConnectorStatusEnumType.Faulted }
+  ])
 }
index b1cd51fc8e935a7cebfe05a935a623ca4e41dc6c..3be92e9e863af639aeab6f2cd73b93c1a16cfef5 100644 (file)
@@ -3,6 +3,7 @@
 import type { ValidateFunction } from 'ajv'
 
 import type { ChargingStation } from '../../../charging-station/index.js'
+
 import { OCPPError } from '../../../exception/index.js'
 import {
   ErrorType,
@@ -10,7 +11,7 @@ import {
   type JsonType,
   type OCPP20ClearCacheRequest,
   OCPP20IncomingRequestCommand,
-  OCPPVersion
+  OCPPVersion,
 } from '../../../types/index.js'
 import { isAsyncFunction, logger } from '../../../utils/index.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
@@ -22,8 +23,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
   protected payloadValidateFunctions: Map<OCPP20IncomingRequestCommand, ValidateFunction<JsonType>>
 
   private readonly incomingRequestHandlers: Map<
-  OCPP20IncomingRequestCommand,
-  IncomingRequestHandler
+    OCPP20IncomingRequestCommand,
+    IncomingRequestHandler
   >
 
   public constructor () {
@@ -32,28 +33,27 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     // }
     super(OCPPVersion.VERSION_201)
     this.incomingRequestHandlers = new Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>([
-      [OCPP20IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)]
+      [OCPP20IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
     ])
     this.payloadValidateFunctions = new Map<
-    OCPP20IncomingRequestCommand,
-    ValidateFunction<JsonType>
+      OCPP20IncomingRequestCommand,
+      ValidateFunction<JsonType>
     >([
       [
         OCPP20IncomingRequestCommand.CLEAR_CACHE,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheRequest>(
-              'assets/json-schemas/ocpp/2.0/ClearCacheRequest.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheRequest>(
+            'assets/json-schemas/ocpp/2.0/ClearCacheRequest.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
     messageId: string,
@@ -79,7 +79,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       )
     }
     if (
-      chargingStation.isRegistered() ||
+      chargingStation.inAcceptedState() ||
+      chargingStation.inPendingState() ||
       (chargingStation.stationInfo?.ocppStrictCompliance === false &&
         chargingStation.inUnknownState())
     ) {
@@ -109,7 +110,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
+          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
             commandPayload,
             undefined,
             2
index c9c4b17b24d6060e7ac102e9ab70664f97c2d13a..e44693658c909b91c6a369d924f1b366a9c610fb 100644 (file)
@@ -3,6 +3,8 @@
 import type { ValidateFunction } from 'ajv'
 
 import type { ChargingStation } from '../../../charging-station/index.js'
+import type { OCPPResponseService } from '../OCPPResponseService.js'
+
 import { OCPPError } from '../../../exception/index.js'
 import {
   ErrorType,
@@ -13,11 +15,10 @@ import {
   OCPP20RequestCommand,
   type OCPP20StatusNotificationRequest,
   OCPPVersion,
-  type RequestParams
+  type RequestParams,
 } from '../../../types/index.js'
 import { generateUUID } from '../../../utils/index.js'
 import { OCPPRequestService } from '../OCPPRequestService.js'
-import type { OCPPResponseService } from '../OCPPResponseService.js'
 import { OCPP20Constants } from './OCPP20Constants.js'
 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
 
@@ -34,44 +35,39 @@ export class OCPP20RequestService extends OCPPRequestService {
     this.payloadValidateFunctions = new Map<OCPP20RequestCommand, ValidateFunction<JsonType>>([
       [
         OCPP20RequestCommand.BOOT_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationRequest>(
-              'assets/json-schemas/ocpp/2.0/BootNotificationRequest.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationRequest>(
+            'assets/json-schemas/ocpp/2.0/BootNotificationRequest.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP20RequestCommand.HEARTBEAT,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatRequest>(
-              'assets/json-schemas/ocpp/2.0/HeartbeatRequest.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatRequest>(
+            'assets/json-schemas/ocpp/2.0/HeartbeatRequest.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP20RequestCommand.STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationRequest>(
-              'assets/json-schemas/ocpp/2.0/StatusNotificationRequest.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationRequest>(
+            'assets/json-schemas/ocpp/2.0/StatusNotificationRequest.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP20RequestCommand,
@@ -80,7 +76,7 @@ export class OCPP20RequestService extends OCPPRequestService {
   ): Promise<ResponseType> {
     // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
     if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
-      // TODO: post request actions hook
+      // TODO: pre request actions hook
       return (await this.sendMessage(
         chargingStation,
         generateUUID(),
@@ -92,12 +88,13 @@ export class OCPP20RequestService extends OCPPRequestService {
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
       ErrorType.NOT_SUPPORTED,
-      `Unsupported OCPP command '${commandName}'`,
+      `Unsupported OCPP command ${commandName}`,
       commandName,
       commandParams
     )
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP20RequestCommand,
@@ -112,14 +109,14 @@ export class OCPP20RequestService extends OCPPRequestService {
       case OCPP20RequestCommand.STATUS_NOTIFICATION:
         return {
           timestamp: new Date(),
-          ...commandParams
+          ...commandParams,
         } as unknown as Request
       default:
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         throw new OCPPError(
           ErrorType.NOT_SUPPORTED,
           // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          `Unsupported OCPP command '${commandName}'`,
+          `Unsupported OCPP command ${commandName}`,
           commandName,
           commandParams
         )
index 6ab91c074d01ec4f24b01a4c5fbdffb374ad1fa9..5a4b62b81316026597ee750b8aa148818ef6bcd4 100644 (file)
@@ -5,6 +5,7 @@ import type { ValidateFunction } from 'ajv'
 import { addConfigurationKey, type ChargingStation } from '../../../charging-station/index.js'
 import { OCPPError } from '../../../exception/index.js'
 import {
+  ChargingStationEvents,
   ErrorType,
   type JsonType,
   type OCPP20BootNotificationResponse,
@@ -16,7 +17,7 @@ import {
   type OCPP20StatusNotificationResponse,
   OCPPVersion,
   RegistrationStatusEnumType,
-  type ResponseHandler
+  type ResponseHandler,
 } from '../../../types/index.js'
 import { isAsyncFunction, logger } from '../../../utils/index.js'
 import { OCPPResponseService } from '../OCPPResponseService.js'
@@ -26,8 +27,8 @@ const moduleName = 'OCPP20ResponseService'
 
 export class OCPP20ResponseService extends OCPPResponseService {
   public incomingRequestResponsePayloadValidateFunctions: Map<
-  OCPP20IncomingRequestCommand,
-  ValidateFunction<JsonType>
+    OCPP20IncomingRequestCommand,
+    ValidateFunction<JsonType>
   >
 
   protected payloadValidateFunctions: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
@@ -41,76 +42,75 @@ export class OCPP20ResponseService extends OCPPResponseService {
     this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
       [
         OCPP20RequestCommand.BOOT_NOTIFICATION,
-        this.handleResponseBootNotification.bind(this) as ResponseHandler
+        this.handleResponseBootNotification.bind(this) as ResponseHandler,
       ],
       [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler],
-      [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler]
+      [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler],
     ])
     this.payloadValidateFunctions = new Map<OCPP20RequestCommand, ValidateFunction<JsonType>>([
       [
         OCPP20RequestCommand.BOOT_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
-              'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
+            'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP20RequestCommand.HEARTBEAT,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
-              'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
+            'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
+        ),
       ],
       [
         OCPP20RequestCommand.STATUS_NOTIFICATION,
-        this.ajv
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
-              'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajv.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
+            'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.incomingRequestResponsePayloadValidateFunctions = new Map<
-    OCPP20IncomingRequestCommand,
-    ValidateFunction<JsonType>
+      OCPP20IncomingRequestCommand,
+      ValidateFunction<JsonType>
     >([
       [
         OCPP20IncomingRequestCommand.CLEAR_CACHE,
-        this.ajvIncomingRequest
-          .compile(
-            OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
-              'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
-              moduleName,
-              'constructor'
-            )
+        this.ajvIncomingRequest.compile(
+          OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
+            'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
+            moduleName,
+            'constructor'
           )
-          .bind(this)
-      ]
+        ),
+      ],
     ])
     this.validatePayload = this.validatePayload.bind(this)
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
     commandName: OCPP20RequestCommand,
     payload: ResType,
     requestPayload: ReqType
   ): Promise<void> {
-    if (chargingStation.isRegistered() || commandName === OCPP20RequestCommand.BOOT_NOTIFICATION) {
+    if (
+      chargingStation.inAcceptedState() ||
+      ((chargingStation.inUnknownState() || chargingStation.inPendingState()) &&
+        commandName === OCPP20RequestCommand.BOOT_NOTIFICATION) ||
+      (chargingStation.stationInfo?.ocppStrictCompliance === false &&
+        (chargingStation.inUnknownState() || chargingStation.inPendingState()))
+    ) {
       if (
         this.responseHandlers.has(commandName) &&
         OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
@@ -122,7 +122,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
           if (isAsyncFunction(responseHandler)) {
             await responseHandler(chargingStation, payload, requestPayload)
           } else {
-            (
+            ;(
               responseHandler as (
                 chargingStation: ChargingStation,
                 payload: JsonType,
@@ -141,7 +141,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `'${commandName}' is not implemented to handle response PDU ${JSON.stringify(
+          `${commandName} is not implemented to handle response PDU ${JSON.stringify(
             payload,
             undefined,
             2
@@ -164,35 +164,29 @@ export class OCPP20ResponseService extends OCPPResponseService {
     }
   }
 
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP20RequestCommand,
-    payload: JsonType
-  ): boolean {
-    if (this.payloadValidateFunctions.has(commandName)) {
-      return this.validateResponsePayload(chargingStation, commandName, payload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
-
   private handleResponseBootNotification (
     chargingStation: ChargingStation,
     payload: OCPP20BootNotificationResponse
   ): void {
-    if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
-      addConfigurationKey(
-        chargingStation,
-        OCPP20OptionalVariableName.HeartbeatInterval,
-        payload.interval.toString(),
-        {},
-        { overwrite: true, save: true }
-      )
-      OCPP20ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
-    }
     if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
+      chargingStation.bootNotificationResponse = payload
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      if (payload.interval != null) {
+        addConfigurationKey(
+          chargingStation,
+          OCPP20OptionalVariableName.HeartbeatInterval,
+          payload.interval.toString(),
+          {},
+          { overwrite: true, save: true }
+        )
+      }
+      if (chargingStation.inAcceptedState()) {
+        chargingStation.emit(ChargingStationEvents.accepted)
+      } else if (chargingStation.inPendingState()) {
+        chargingStation.emit(ChargingStationEvents.pending)
+      } else if (chargingStation.inRejectedState()) {
+        chargingStation.emit(ChargingStationEvents.rejected)
+      }
       const logMsg = `${chargingStation.logPrefix()} Charging station in '${
         payload.status
       }' state on the central server`
@@ -200,10 +194,25 @@ export class OCPP20ResponseService extends OCPPResponseService {
         ? logger.warn(logMsg)
         : logger.info(logMsg)
     } else {
+      delete chargingStation.bootNotificationResponse
       logger.error(
         `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
         payload
       )
     }
   }
+
+  private validatePayload (
+    chargingStation: ChargingStation,
+    commandName: OCPP20RequestCommand,
+    payload: JsonType
+  ): boolean {
+    if (this.payloadValidateFunctions.has(commandName)) {
+      return this.validateResponsePayload(chargingStation, commandName, payload)
+    }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+    )
+    return false
+  }
 }
index 75d61731e8c72c00db49e075d7f1af6e311207d6..82bd9f133a0a54426ccea7465edbd0bbe2fd5479 100644 (file)
@@ -6,12 +6,12 @@ import { type JsonType, OCPPVersion } from '../../../types/index.js'
 import { OCPPServiceUtils } from '../OCPPServiceUtils.js'
 
 export class OCPP20ServiceUtils extends OCPPServiceUtils {
-  public static parseJsonSchemaFile<T extends JsonType>(
+  public static override parseJsonSchemaFile<T extends JsonType>(
     relativePath: string,
     moduleName?: string,
     methodName?: string
   ): JSONSchemaType<T> {
-    return super.parseJsonSchemaFile<T>(
+    return OCPPServiceUtils.parseJsonSchemaFile<T>(
       relativePath,
       OCPPVersion.VERSION_201,
       moduleName,
index b036738719944fae935f62c03ec137c06acca761..6f530edf0bb3e05a2d99399d1b8931aa4002e557 100644 (file)
@@ -8,135 +8,152 @@ import {
   MeterValueMeasurand,
   ReservationStatus,
   TriggerMessageStatus,
-  UnlockStatus
+  UnlockStatus,
 } from '../../types/index.js'
 import { Constants } from '../../utils/index.js'
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPConstants {
-  static readonly OCPP_WEBSOCKET_TIMEOUT = 60000 // Ms
+  static readonly OCPP_AVAILABILITY_RESPONSE_ACCEPTED = Object.freeze({
+    status: AvailabilityStatus.ACCEPTED,
+  })
 
-  static readonly OCPP_MEASURANDS_SUPPORTED = Object.freeze([
-    MeterValueMeasurand.STATE_OF_CHARGE,
-    MeterValueMeasurand.VOLTAGE,
-    MeterValueMeasurand.POWER_ACTIVE_IMPORT,
-    MeterValueMeasurand.CURRENT_IMPORT,
-    MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-  ])
+  static readonly OCPP_AVAILABILITY_RESPONSE_REJECTED = Object.freeze({
+    status: AvailabilityStatus.REJECTED,
+  })
 
-  static readonly OCPP_REQUEST_EMPTY = Constants.EMPTY_FROZEN_OBJECT
-  static readonly OCPP_RESPONSE_EMPTY = Constants.EMPTY_FROZEN_OBJECT
-  static readonly OCPP_RESPONSE_ACCEPTED = Object.freeze({ status: GenericStatus.Accepted })
-  static readonly OCPP_RESPONSE_REJECTED = Object.freeze({ status: GenericStatus.Rejected })
+  static readonly OCPP_AVAILABILITY_RESPONSE_SCHEDULED = Object.freeze({
+    status: AvailabilityStatus.SCHEDULED,
+  })
 
-  static readonly OCPP_CONFIGURATION_RESPONSE_ACCEPTED = Object.freeze({
-    status: ConfigurationStatus.ACCEPTED
+  // Reservation for id has been cancelled
+  static readonly OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED = Object.freeze({
+    status: GenericStatus.Accepted,
   })
 
-  static readonly OCPP_CONFIGURATION_RESPONSE_REJECTED = Object.freeze({
-    status: ConfigurationStatus.REJECTED
+  // Reservation could not be cancelled, because there is no reservation active for id
+  static readonly OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED = Object.freeze({
+    status: GenericStatus.Rejected,
   })
 
-  static readonly OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED = Object.freeze({
-    status: ConfigurationStatus.REBOOT_REQUIRED
+  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({
+    status: ClearChargingProfileStatus.ACCEPTED,
   })
 
-  static readonly OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED = Object.freeze({
-    status: ConfigurationStatus.NOT_SUPPORTED
+  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN = Object.freeze({
+    status: ClearChargingProfileStatus.UNKNOWN,
   })
 
-  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({
-    status: ChargingProfileStatus.ACCEPTED
+  static readonly OCPP_CONFIGURATION_RESPONSE_ACCEPTED = Object.freeze({
+    status: ConfigurationStatus.ACCEPTED,
   })
 
-  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED = Object.freeze({
-    status: ChargingProfileStatus.REJECTED
+  static readonly OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED = Object.freeze({
+    status: ConfigurationStatus.NOT_SUPPORTED,
   })
 
-  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED = Object.freeze({
-    status: ChargingProfileStatus.NOT_SUPPORTED
+  static readonly OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED = Object.freeze({
+    status: ConfigurationStatus.REBOOT_REQUIRED,
   })
 
-  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({
-    status: ClearChargingProfileStatus.ACCEPTED
+  static readonly OCPP_CONFIGURATION_RESPONSE_REJECTED = Object.freeze({
+    status: ConfigurationStatus.REJECTED,
   })
 
-  static readonly OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN = Object.freeze({
-    status: ClearChargingProfileStatus.UNKNOWN
+  static readonly OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED = Object.freeze({
+    status: DataTransferStatus.ACCEPTED,
   })
 
-  static readonly OCPP_RESPONSE_UNLOCKED = Object.freeze({ status: UnlockStatus.UNLOCKED })
-  static readonly OCPP_RESPONSE_UNLOCK_FAILED = Object.freeze({
-    status: UnlockStatus.UNLOCK_FAILED
+  static readonly OCPP_DATA_TRANSFER_RESPONSE_REJECTED = Object.freeze({
+    status: DataTransferStatus.REJECTED,
   })
 
-  static readonly OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED = Object.freeze({
-    status: UnlockStatus.NOT_SUPPORTED
+  static readonly OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID = Object.freeze({
+    status: DataTransferStatus.UNKNOWN_VENDOR_ID,
   })
 
-  static readonly OCPP_AVAILABILITY_RESPONSE_ACCEPTED = Object.freeze({
-    status: AvailabilityStatus.ACCEPTED
+  static readonly OCPP_MEASURANDS_SUPPORTED = Object.freeze([
+    MeterValueMeasurand.STATE_OF_CHARGE,
+    MeterValueMeasurand.VOLTAGE,
+    MeterValueMeasurand.POWER_ACTIVE_IMPORT,
+    MeterValueMeasurand.CURRENT_IMPORT,
+    MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+  ])
+
+  static readonly OCPP_REQUEST_EMPTY = Constants.EMPTY_FROZEN_OBJECT
+
+  // Reservation has been made
+  static readonly OCPP_RESERVATION_RESPONSE_ACCEPTED = Object.freeze({
+    status: ReservationStatus.ACCEPTED,
   })
 
-  static readonly OCPP_AVAILABILITY_RESPONSE_REJECTED = Object.freeze({
-    status: AvailabilityStatus.REJECTED
+  // Reservation has not been made, because of connector in FAULTED state
+  static readonly OCPP_RESERVATION_RESPONSE_FAULTED = Object.freeze({
+    status: ReservationStatus.FAULTED,
   })
 
-  static readonly OCPP_AVAILABILITY_RESPONSE_SCHEDULED = Object.freeze({
-    status: AvailabilityStatus.SCHEDULED
+  // Reservation has not been made, because all connectors are OCCUPIED
+  static readonly OCPP_RESERVATION_RESPONSE_OCCUPIED = Object.freeze({
+    status: ReservationStatus.OCCUPIED,
   })
 
-  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED = Object.freeze({
-    status: TriggerMessageStatus.ACCEPTED
+  // Reservation has not been made, because charging station is not configured to accept reservations
+  static readonly OCPP_RESERVATION_RESPONSE_REJECTED = Object.freeze({
+    status: ReservationStatus.REJECTED,
   })
 
-  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED = Object.freeze({
-    status: TriggerMessageStatus.REJECTED
+  // Reservation has not been made, because connector is in UNAVAILABLE state
+  static readonly OCPP_RESERVATION_RESPONSE_UNAVAILABLE = Object.freeze({
+    status: ReservationStatus.UNAVAILABLE,
   })
 
-  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED = Object.freeze({
-    status: TriggerMessageStatus.NOT_IMPLEMENTED
+  static readonly OCPP_RESPONSE_ACCEPTED = Object.freeze({
+    status: GenericStatus.Accepted,
   })
 
-  static readonly OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED = Object.freeze({
-    status: DataTransferStatus.ACCEPTED
+  static readonly OCPP_RESPONSE_EMPTY = Constants.EMPTY_FROZEN_OBJECT
+
+  static readonly OCPP_RESPONSE_REJECTED = Object.freeze({
+    status: GenericStatus.Rejected,
   })
 
-  static readonly OCPP_DATA_TRANSFER_RESPONSE_REJECTED = Object.freeze({
-    status: DataTransferStatus.REJECTED
+  static readonly OCPP_RESPONSE_UNLOCK_FAILED = Object.freeze({
+    status: UnlockStatus.UNLOCK_FAILED,
   })
 
-  static readonly OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID = Object.freeze({
-    status: DataTransferStatus.UNKNOWN_VENDOR_ID
+  static readonly OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED = Object.freeze({
+    status: UnlockStatus.NOT_SUPPORTED,
   })
 
-  static readonly OCPP_RESERVATION_RESPONSE_ACCEPTED = Object.freeze({
-    status: ReservationStatus.ACCEPTED
-  }) // Reservation has been made
+  static readonly OCPP_RESPONSE_UNLOCKED = Object.freeze({
+    status: UnlockStatus.UNLOCKED,
+  })
 
-  static readonly OCPP_RESERVATION_RESPONSE_FAULTED = Object.freeze({
-    status: ReservationStatus.FAULTED
-  }) // Reservation has not been made, because of connector in FAULTED state
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED = Object.freeze({
+    status: ChargingProfileStatus.ACCEPTED,
+  })
 
-  static readonly OCPP_RESERVATION_RESPONSE_OCCUPIED = Object.freeze({
-    status: ReservationStatus.OCCUPIED
-  }) // Reservation has not been made, because all connectors are OCCUPIED
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED = Object.freeze({
+    status: ChargingProfileStatus.NOT_SUPPORTED,
+  })
 
-  static readonly OCPP_RESERVATION_RESPONSE_REJECTED = Object.freeze({
-    status: ReservationStatus.REJECTED
-  }) // Reservation has not been made, because charging station is not configured to accept reservations
+  static readonly OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED = Object.freeze({
+    status: ChargingProfileStatus.REJECTED,
+  })
 
-  static readonly OCPP_RESERVATION_RESPONSE_UNAVAILABLE = Object.freeze({
-    status: ReservationStatus.UNAVAILABLE
-  }) // Reservation has not been made, because connector is in UNAVAILABLE state
+  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED = Object.freeze({
+    status: TriggerMessageStatus.ACCEPTED,
+  })
 
-  static readonly OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED = Object.freeze({
-    status: GenericStatus.Accepted
-  }) // Reservation for id has been cancelled
+  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED = Object.freeze({
+    status: TriggerMessageStatus.NOT_IMPLEMENTED,
+  })
 
-  static readonly OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED = Object.freeze({
-    status: GenericStatus.Rejected
-  }) // Reservation could not be cancelled, because there is no reservation active for id
+  static readonly OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED = Object.freeze({
+    status: TriggerMessageStatus.REJECTED,
+  })
+
+  static readonly OCPP_WEBSOCKET_TIMEOUT = 60000 // Ms
 
   protected constructor () {
     // This is intentional
index 7c88547a6c63ce285e2009e6561b9d813fc99b53..15981f7e42c22513d5f074a9ee840636da50a3f4 100644 (file)
@@ -1,20 +1,20 @@
-import { EventEmitter } from 'node:events'
-
 import _Ajv, { type ValidateFunction } from 'ajv'
 import _ajvFormats from 'ajv-formats'
+import { EventEmitter } from 'node:events'
 
-import { type ChargingStation, getIdTagsFile } from '../../charging-station/index.js'
-import { OCPPError } from '../../exception/index.js'
 import type {
   ClearCacheResponse,
-  HandleErrorParams,
   IncomingRequestCommand,
   JsonType,
-  OCPPVersion
+  OCPPVersion,
 } from '../../types/index.js'
-import { logger, setDefaultErrorParams } from '../../utils/index.js'
+
+import { type ChargingStation, getIdTagsFile } from '../../charging-station/index.js'
+import { OCPPError } from '../../exception/index.js'
+import { logger } from '../../utils/index.js'
 import { OCPPConstants } from './OCPPConstants.js'
-import { OCPPServiceUtils } from './OCPPServiceUtils.js'
+import { ajvErrorsToErrorType } from './OCPPServiceUtils.js'
+
 type Ajv = _Ajv.default
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 const Ajv = _Ajv.default
@@ -23,20 +23,21 @@ const ajvFormats = _ajvFormats.default
 const moduleName = 'OCPPIncomingRequestService'
 
 export abstract class OCPPIncomingRequestService extends EventEmitter {
-  private static instance: OCPPIncomingRequestService | null = null
-  private readonly version: OCPPVersion
+  private static instance: null | OCPPIncomingRequestService = null
   protected readonly ajv: Ajv
   protected abstract payloadValidateFunctions: Map<
-  IncomingRequestCommand,
-  ValidateFunction<JsonType>
+    IncomingRequestCommand,
+    ValidateFunction<JsonType>
   >
 
+  private readonly version: OCPPVersion
+
   protected constructor (version: OCPPVersion) {
     super()
     this.version = version
     this.ajv = new Ajv({
       keywords: ['javaType'],
-      multipleOfPrecision: 2
+      multipleOfPrecision: 2,
     })
     ajvFormats(this.ajv)
     this.incomingRequestHandler = this.incomingRequestHandler.bind(this)
@@ -44,34 +45,27 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
   }
 
   public static getInstance<T extends OCPPIncomingRequestService>(this: new () => T): T {
-    if (OCPPIncomingRequestService.instance === null) {
-      OCPPIncomingRequestService.instance = new this()
-    }
+    OCPPIncomingRequestService.instance ??= new this()
     return OCPPIncomingRequestService.instance as T
   }
 
-  protected handleIncomingRequestError<T extends JsonType>(
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unnecessary-type-parameters
+  public abstract incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
     chargingStation: ChargingStation,
+    messageId: string,
     commandName: IncomingRequestCommand,
-    error: Error,
-    params: HandleErrorParams<T> = { throwError: true, consoleOut: false }
-  ): T | undefined {
-    setDefaultErrorParams(params)
-    logger.error(
-      `${chargingStation.logPrefix()} ${moduleName}.handleIncomingRequestError: Incoming request command '${commandName}' error:`,
-      error
-    )
-    if (params.throwError === false && params.errorResponse != null) {
-      return params.errorResponse
-    }
-    if (params.throwError === true && params.errorResponse == null) {
-      throw error
-    }
-    if (params.throwError === true && params.errorResponse != null) {
-      return params.errorResponse
+    commandPayload: ReqType
+  ): Promise<void>
+
+  protected handleRequestClearCache (chargingStation: ChargingStation): ClearCacheResponse {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (chargingStation.idTagsCache.deleteIdTags(getIdTagsFile(chargingStation.stationInfo!)!)) {
+      return OCPPConstants.OCPP_RESPONSE_ACCEPTED
     }
+    return OCPPConstants.OCPP_RESPONSE_REJECTED
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   protected validateIncomingRequestPayload<T extends JsonType>(
     chargingStation: ChargingStation,
     commandName: IncomingRequestCommand,
@@ -89,26 +83,10 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
       validate?.errors
     )
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate?.errors),
       'Incoming request PDU is invalid',
       commandName,
       JSON.stringify(validate?.errors, undefined, 2)
     )
   }
-
-  protected handleRequestClearCache (chargingStation: ChargingStation): ClearCacheResponse {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (chargingStation.idTagsCache.deleteIdTags(getIdTagsFile(chargingStation.stationInfo!)!)) {
-      return OCPPConstants.OCPP_RESPONSE_ACCEPTED
-    }
-    return OCPPConstants.OCPP_RESPONSE_REJECTED
-  }
-
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  public abstract incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    messageId: string,
-    commandName: IncomingRequestCommand,
-    commandPayload: ReqType
-  ): Promise<void>
 }
index 3a27a1923d7a0234efed3c223337e7e26ba2347f..439d656fbd1da7480a0998905c8f1b181592795c 100644 (file)
@@ -2,6 +2,8 @@ import _Ajv, { type ValidateFunction } from 'ajv'
 import _ajvFormats from 'ajv-formats'
 
 import type { ChargingStation } from '../../charging-station/index.js'
+import type { OCPPResponseService } from './OCPPResponseService.js'
+
 import { OCPPError } from '../../exception/index.js'
 import { PerformanceStatistics } from '../../performance/index.js'
 import {
@@ -18,17 +20,21 @@ import {
   type RequestParams,
   type Response,
   type ResponseCallback,
-  type ResponseType
+  type ResponseType,
 } from '../../types/index.js'
 import {
   clone,
   formatDurationMilliSeconds,
   handleSendMessageError,
-  logger
+  logger,
 } from '../../utils/index.js'
 import { OCPPConstants } from './OCPPConstants.js'
-import type { OCPPResponseService } from './OCPPResponseService.js'
-import { OCPPServiceUtils } from './OCPPServiceUtils.js'
+import {
+  ajvErrorsToErrorType,
+  convertDateToISOString,
+  getMessageTypeString,
+} from './OCPPServiceUtils.js'
+
 type Ajv = _Ajv.default
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 const Ajv = _Ajv.default
@@ -38,22 +44,22 @@ const moduleName = 'OCPPRequestService'
 
 const defaultRequestParams: RequestParams = {
   skipBufferingOnError: false,
+  throwError: false,
   triggerMessage: false,
-  throwError: false
 }
 
 export abstract class OCPPRequestService {
-  private static instance: OCPPRequestService | null = null
-  private readonly version: OCPPVersion
-  private readonly ocppResponseService: OCPPResponseService
+  private static instance: null | OCPPRequestService = null
   protected readonly ajv: Ajv
   protected abstract payloadValidateFunctions: Map<RequestCommand, ValidateFunction<JsonType>>
+  private readonly ocppResponseService: OCPPResponseService
+  private readonly version: OCPPVersion
 
   protected constructor (version: OCPPVersion, ocppResponseService: OCPPResponseService) {
     this.version = version
     this.ajv = new Ajv({
       keywords: ['javaType'],
-      multipleOfPrecision: 2
+      multipleOfPrecision: 2,
     })
     ajvFormats(this.ajv)
     this.ocppResponseService = ocppResponseService
@@ -72,52 +78,69 @@ export abstract class OCPPRequestService {
     this: new (ocppResponseService: OCPPResponseService) => T,
     ocppResponseService: OCPPResponseService
   ): T {
-    if (OCPPRequestService.instance === null) {
-      OCPPRequestService.instance = new this(ocppResponseService)
-    }
+    OCPPRequestService.instance ??= new this(ocppResponseService)
     return OCPPRequestService.instance as T
   }
 
-  public async sendResponse (
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public abstract requestHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: RequestCommand,
+    commandParams?: ReqType,
+    params?: RequestParams
+  ): Promise<ResType>
+
+  public async sendError (
     chargingStation: ChargingStation,
     messageId: string,
-    messagePayload: JsonType,
-    commandName: IncomingRequestCommand
+    ocppError: OCPPError,
+    commandName: IncomingRequestCommand | RequestCommand
   ): Promise<ResponseType> {
     try {
-      // Send response message
+      // Send error message
       return await this.internalSendMessage(
         chargingStation,
         messageId,
-        messagePayload,
-        MessageType.CALL_RESULT_MESSAGE,
+        ocppError,
+        MessageType.CALL_ERROR_MESSAGE,
         commandName
       )
     } catch (error) {
-      handleSendMessageError(chargingStation, commandName, error as Error, {
-        throwError: true
-      })
+      handleSendMessageError(
+        chargingStation,
+        commandName,
+        MessageType.CALL_ERROR_MESSAGE,
+        error as Error
+      )
       return null
     }
   }
 
-  public async sendError (
+  public async sendResponse (
     chargingStation: ChargingStation,
     messageId: string,
-    ocppError: OCPPError,
-    commandName: RequestCommand | IncomingRequestCommand
+    messagePayload: JsonType,
+    commandName: IncomingRequestCommand
   ): Promise<ResponseType> {
     try {
-      // Send error message
+      // Send response message
       return await this.internalSendMessage(
         chargingStation,
         messageId,
-        ocppError,
-        MessageType.CALL_ERROR_MESSAGE,
+        messagePayload,
+        MessageType.CALL_RESULT_MESSAGE,
         commandName
       )
     } catch (error) {
-      handleSendMessageError(chargingStation, commandName, error as Error)
+      handleSendMessageError(
+        chargingStation,
+        commandName,
+        MessageType.CALL_RESULT_MESSAGE,
+        error as Error,
+        {
+          throwError: true,
+        }
+      )
       return null
     }
   }
@@ -131,7 +154,7 @@ export abstract class OCPPRequestService {
   ): Promise<ResponseType> {
     params = {
       ...defaultRequestParams,
-      ...params
+      ...params,
     }
     try {
       return await this.internalSendMessage(
@@ -143,83 +166,69 @@ export abstract class OCPPRequestService {
         params
       )
     } catch (error) {
-      handleSendMessageError(chargingStation, commandName, error as Error, {
-        throwError: params.throwError
-      })
-      return null
-    }
-  }
-
-  private validateRequestPayload<T extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: RequestCommand | IncomingRequestCommand,
-    payload: T
-  ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    if (!this.payloadValidateFunctions.has(commandName as RequestCommand)) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation`
+      handleSendMessageError(
+        chargingStation,
+        commandName,
+        MessageType.CALL_MESSAGE,
+        error as Error,
+        {
+          throwError: params.throwError,
+        }
       )
-      return true
-    }
-    const validate = this.payloadValidateFunctions.get(commandName as RequestCommand)
-    payload = clone<T>(payload)
-    OCPPServiceUtils.convertDateToISOString<T>(payload)
-    if (validate?.(payload) === true) {
-      return true
+      return null
     }
-    logger.error(
-      `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`,
-      validate?.errors
-    )
-    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
-    throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate?.errors),
-      'Request PDU is invalid',
-      commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
-    )
   }
 
-  private validateIncomingRequestResponsePayload<T extends JsonType>(
+  private buildMessageToSend (
     chargingStation: ChargingStation,
-    commandName: RequestCommand | IncomingRequestCommand,
-    payload: T
-  ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    if (
-      !this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.has(
-        commandName as IncomingRequestCommand
-      )
-    ) {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-      )
-      return true
-    }
-    const validate = this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get(
-      commandName as IncomingRequestCommand
-    )
-    payload = clone<T>(payload)
-    OCPPServiceUtils.convertDateToISOString<T>(payload)
-    if (validate?.(payload) === true) {
-      return true
+    messageId: string,
+    messagePayload: JsonType | OCPPError,
+    messageType: MessageType,
+    commandName: IncomingRequestCommand | RequestCommand
+  ): string {
+    let messageToSend: string
+    // Type of message
+    switch (messageType) {
+      // Error Message
+      case MessageType.CALL_ERROR_MESSAGE:
+        // Build Error Message
+        messageToSend = JSON.stringify([
+          messageType,
+          messageId,
+          (messagePayload as OCPPError).code,
+          (messagePayload as OCPPError).message,
+          (messagePayload as OCPPError).details ?? {
+            command: (messagePayload as OCPPError).command,
+          },
+        ] satisfies ErrorResponse)
+        break
+      // Request
+      case MessageType.CALL_MESSAGE:
+        // Build request
+        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType)
+        messageToSend = JSON.stringify([
+          messageType,
+          messageId,
+          commandName as RequestCommand,
+          messagePayload as JsonType,
+        ] satisfies OutgoingRequest)
+        break
+      // Response
+      case MessageType.CALL_RESULT_MESSAGE:
+        // Build response
+        this.validateIncomingRequestResponsePayload(
+          chargingStation,
+          commandName,
+          messagePayload as JsonType
+        )
+        messageToSend = JSON.stringify([
+          messageType,
+          messageId,
+          messagePayload as JsonType,
+        ] satisfies Response)
+        break
     }
-    logger.error(
-      `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' response PDU is invalid: %j`,
-      validate?.errors
-    )
-    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
-    throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate?.errors),
-      'Response PDU is invalid',
-      commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
-    )
+    return messageToSend
   }
 
   private async internalSendMessage (
@@ -227,17 +236,18 @@ export abstract class OCPPRequestService {
     messageId: string,
     messagePayload: JsonType | OCPPError,
     messageType: MessageType,
-    commandName: RequestCommand | IncomingRequestCommand,
+    commandName: IncomingRequestCommand | RequestCommand,
     params?: RequestParams
   ): Promise<ResponseType> {
     params = {
       ...defaultRequestParams,
-      ...params
+      ...params,
     }
     if (
-      (chargingStation.inUnknownState() && commandName === RequestCommand.BOOT_NOTIFICATION) ||
+      ((chargingStation.inUnknownState() || chargingStation.inPendingState()) &&
+        commandName === RequestCommand.BOOT_NOTIFICATION) ||
       (chargingStation.stationInfo?.ocppStrictCompliance === false &&
-        chargingStation.inUnknownState()) ||
+        (chargingStation.inUnknownState() || chargingStation.inPendingState())) ||
       chargingStation.inAcceptedState() ||
       (chargingStation.inPendingState() &&
         (params.triggerMessage === true || messageType === MessageType.CALL_RESULT_MESSAGE))
@@ -248,7 +258,6 @@ export abstract class OCPPRequestService {
       return await new Promise<ResponseType>((resolve, reject: (reason?: unknown) => void) => {
         /**
          * Function that will receive the request's response
-         *
          * @param payload -
          * @param requestPayload -
          */
@@ -269,17 +278,17 @@ export abstract class OCPPRequestService {
             )
             .then(() => {
               resolve(payload)
+              return undefined
             })
-            .catch(reject)
             .finally(() => {
               chargingStation.requests.delete(messageId)
               chargingStation.emit(ChargingStationEvents.updated)
             })
+            .catch(reject)
         }
 
         /**
          * Function that will receive the request's error response
-         *
          * @param ocppError -
          * @param requestStatistic -
          */
@@ -291,7 +300,7 @@ export abstract class OCPPRequestService {
             )
           }
           logger.error(
-            `${chargingStation.logPrefix()} Error occurred at ${OCPPServiceUtils.getMessageTypeString(
+            `${chargingStation.logPrefix()} Error occurred at ${getMessageTypeString(
               messageType
             )} command ${commandName} with PDU %j:`,
             messagePayload,
@@ -358,7 +367,7 @@ export abstract class OCPPRequestService {
             clearTimeout(sendTimeout)
             if (error == null) {
               logger.debug(
-                `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${OCPPServiceUtils.getMessageTypeString(
+                `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${getMessageTypeString(
                   messageType
                 )} payload: ${messageToSend}`
               )
@@ -383,7 +392,11 @@ export abstract class OCPPRequestService {
                     params.skipBufferingOnError === false ? '' : 'non '
                   }buffered message id '${messageId}' with content '${messageToSend}'`,
                   commandName,
-                  { name: error.name, message: error.message, stack: error.stack }
+                  {
+                    message: error.message,
+                    name: error.name,
+                    stack: error.stack,
+                  }
                 )
               )
             }
@@ -404,68 +417,17 @@ export abstract class OCPPRequestService {
     }
     throw new OCPPError(
       ErrorType.SECURITY_ERROR,
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.bootNotificationResponse?.status} state on the central server`,
       commandName
     )
   }
 
-  private buildMessageToSend (
-    chargingStation: ChargingStation,
-    messageId: string,
-    messagePayload: JsonType | OCPPError,
-    messageType: MessageType,
-    commandName: RequestCommand | IncomingRequestCommand
-  ): string {
-    let messageToSend: string
-    // Type of message
-    switch (messageType) {
-      // Request
-      case MessageType.CALL_MESSAGE:
-        // Build request
-        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType)
-        messageToSend = JSON.stringify([
-          messageType,
-          messageId,
-          commandName as RequestCommand,
-          messagePayload as JsonType
-        ] satisfies OutgoingRequest)
-        break
-      // Response
-      case MessageType.CALL_RESULT_MESSAGE:
-        // Build response
-        this.validateIncomingRequestResponsePayload(
-          chargingStation,
-          commandName,
-          messagePayload as JsonType
-        )
-        messageToSend = JSON.stringify([
-          messageType,
-          messageId,
-          messagePayload as JsonType
-        ] satisfies Response)
-        break
-      // Error Message
-      case MessageType.CALL_ERROR_MESSAGE:
-        // Build Error Message
-        messageToSend = JSON.stringify([
-          messageType,
-          messageId,
-          (messagePayload as OCPPError).code,
-          (messagePayload as OCPPError).message,
-          (messagePayload as OCPPError).details ?? {
-            command: (messagePayload as OCPPError).command
-          }
-        ] satisfies ErrorResponse)
-        break
-    }
-    return messageToSend
-  }
-
   private setCachedRequest (
     chargingStation: ChargingStation,
     messageId: string,
     messagePayload: JsonType,
-    commandName: RequestCommand | IncomingRequestCommand,
+    commandName: IncomingRequestCommand | RequestCommand,
     responseCallback: ResponseCallback,
     errorCallback: ErrorCallback
   ): void {
@@ -473,14 +435,81 @@ export abstract class OCPPRequestService {
       responseCallback,
       errorCallback,
       commandName,
-      messagePayload
+      messagePayload,
     ])
   }
 
-  public abstract requestHandler<ReqType extends JsonType, ResType extends JsonType>(
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  private validateIncomingRequestResponsePayload<T extends JsonType>(
     chargingStation: ChargingStation,
-    commandName: RequestCommand,
-    commandParams?: ReqType,
-    params?: RequestParams
-  ): Promise<ResType>
+    commandName: IncomingRequestCommand | RequestCommand,
+    payload: T
+  ): boolean {
+    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
+      return true
+    }
+    if (
+      !this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.has(
+        commandName as IncomingRequestCommand
+      )
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+      )
+      return true
+    }
+    const validate = this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get(
+      commandName as IncomingRequestCommand
+    )
+    payload = clone<T>(payload)
+    convertDateToISOString<T>(payload)
+    if (validate?.(payload) === true) {
+      return true
+    }
+    logger.error(
+      `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' incoming request response PDU is invalid: %j`,
+      validate?.errors
+    )
+    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+    throw new OCPPError(
+      ajvErrorsToErrorType(validate?.errors),
+      'Incoming request response PDU is invalid',
+      commandName,
+      JSON.stringify(validate?.errors, undefined, 2)
+    )
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  private validateRequestPayload<T extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: IncomingRequestCommand | RequestCommand,
+    payload: T
+  ): boolean {
+    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
+      return true
+    }
+    if (!this.payloadValidateFunctions.has(commandName as RequestCommand)) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation`
+      )
+      return true
+    }
+    const validate = this.payloadValidateFunctions.get(commandName as RequestCommand)
+    payload = clone<T>(payload)
+    convertDateToISOString<T>(payload)
+    if (validate?.(payload) === true) {
+      return true
+    }
+    logger.error(
+      `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`,
+      validate?.errors
+    )
+    // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+    throw new OCPPError(
+      ajvErrorsToErrorType(validate?.errors),
+      'Request PDU is invalid',
+      commandName,
+      JSON.stringify(validate?.errors, undefined, 2)
+    )
+  }
 }
index 0e9c6164d299a927dd8adf3f58a15d7247e6ced6..420358497d8670550a93c59ee78d648719da2d9e 100644 (file)
@@ -2,15 +2,17 @@ import _Ajv, { type ValidateFunction } from 'ajv'
 import _ajvFormats from 'ajv-formats'
 
 import type { ChargingStation } from '../../charging-station/index.js'
-import { OCPPError } from '../../exception/index.js'
 import type {
   IncomingRequestCommand,
   JsonType,
   OCPPVersion,
-  RequestCommand
+  RequestCommand,
 } from '../../types/index.js'
+
+import { OCPPError } from '../../exception/index.js'
 import { Constants, logger } from '../../utils/index.js'
-import { OCPPServiceUtils } from './OCPPServiceUtils.js'
+import { ajvErrorsToErrorType } from './OCPPServiceUtils.js'
+
 type Ajv = _Ajv.default
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 const Ajv = _Ajv.default
@@ -19,26 +21,28 @@ const ajvFormats = _ajvFormats.default
 const moduleName = 'OCPPResponseService'
 
 export abstract class OCPPResponseService {
-  private static instance: OCPPResponseService | null = null
-  private readonly version: OCPPVersion
+  private static instance: null | OCPPResponseService = null
+  public abstract incomingRequestResponsePayloadValidateFunctions: Map<
+    IncomingRequestCommand,
+    ValidateFunction<JsonType>
+  >
+
   protected readonly ajv: Ajv
   protected readonly ajvIncomingRequest: Ajv
+  protected emptyResponseHandler = Constants.EMPTY_FUNCTION
   protected abstract payloadValidateFunctions: Map<RequestCommand, ValidateFunction<JsonType>>
-  public abstract incomingRequestResponsePayloadValidateFunctions: Map<
-  IncomingRequestCommand,
-  ValidateFunction<JsonType>
-  >
+  private readonly version: OCPPVersion
 
   protected constructor (version: OCPPVersion) {
     this.version = version
     this.ajv = new Ajv({
       keywords: ['javaType'],
-      multipleOfPrecision: 2
+      multipleOfPrecision: 2,
     })
     ajvFormats(this.ajv)
     this.ajvIncomingRequest = new Ajv({
       keywords: ['javaType'],
-      multipleOfPrecision: 2
+      multipleOfPrecision: 2,
     })
     ajvFormats(this.ajvIncomingRequest)
     this.responseHandler = this.responseHandler.bind(this)
@@ -46,12 +50,19 @@ export abstract class OCPPResponseService {
   }
 
   public static getInstance<T extends OCPPResponseService>(this: new () => T): T {
-    if (OCPPResponseService.instance === null) {
-      OCPPResponseService.instance = new this()
-    }
+    OCPPResponseService.instance ??= new this()
     return OCPPResponseService.instance as T
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  public abstract responseHandler<ReqType extends JsonType, ResType extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: RequestCommand,
+    payload: ResType,
+    requestPayload: ReqType
+  ): Promise<void>
+
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   protected validateResponsePayload<T extends JsonType>(
     chargingStation: ChargingStation,
     commandName: RequestCommand,
@@ -69,19 +80,10 @@ export abstract class OCPPResponseService {
       validate?.errors
     )
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate?.errors),
       'Response PDU is invalid',
       commandName,
       JSON.stringify(validate?.errors, undefined, 2)
     )
   }
-
-  protected emptyResponseHandler = Constants.EMPTY_FUNCTION
-
-  public abstract responseHandler<ReqType extends JsonType, ResType extends JsonType>(
-    chargingStation: ChargingStation,
-    commandName: RequestCommand,
-    payload: ResType,
-    requestPayload: ReqType
-  ): Promise<void>
 }
index 256544fa50090a5a88319f08fdcbe47c2c845f05..ab01c167e755454eed39ccdf73e445a5012ba239 100644 (file)
@@ -1,15 +1,15 @@
+import type { ErrorObject, JSONSchemaType } from 'ajv'
+
+import { isDate } from 'date-fns'
 import { randomInt } from 'node:crypto'
 import { readFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 
-import type { DefinedError, ErrorObject, JSONSchemaType } from 'ajv'
-import { isDate } from 'date-fns'
-
 import {
   type ChargingStation,
   getConfigurationKey,
-  getIdTagsFile
+  getIdTagsFile,
 } from '../../charging-station/index.js'
 import { BaseError, OCPPError } from '../../exception/index.js'
 import {
@@ -45,7 +45,7 @@ import {
   type SampledValueTemplate,
   StandardParametersKey,
   type StatusNotificationRequest,
-  type StatusNotificationResponse
+  type StatusNotificationResponse,
 } from '../../types/index.js'
 import {
   ACElectricUtils,
@@ -62,7 +62,7 @@ import {
   logPrefix,
   max,
   min,
-  roundTo
+  roundTo,
 } from '../../utils/index.js'
 import { OCPP16Constants } from './1.6/OCPP16Constants.js'
 import { OCPP20Constants } from './2.0/OCPP20Constants.js'
@@ -70,12 +70,12 @@ import { OCPPConstants } from './OCPPConstants.js'
 
 export const getMessageTypeString = (messageType: MessageType | undefined): string => {
   switch (messageType) {
+    case MessageType.CALL_ERROR_MESSAGE:
+      return 'error'
     case MessageType.CALL_MESSAGE:
       return 'request'
     case MessageType.CALL_RESULT_MESSAGE:
       return 'response'
-    case MessageType.CALL_ERROR_MESSAGE:
-      return 'error'
     default:
       return 'unknown'
   }
@@ -91,17 +91,17 @@ const buildStatusNotificationRequest = (
     case OCPPVersion.VERSION_16:
       return {
         connectorId,
+        errorCode: ChargePointErrorCode.NO_ERROR,
         status: status as OCPP16ChargePointStatus,
-        errorCode: ChargePointErrorCode.NO_ERROR
       } satisfies OCPP16StatusNotificationRequest
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201:
       return {
-        timestamp: new Date(),
-        connectorStatus: status as OCPP20ConnectorStatusEnumType,
         connectorId,
+        connectorStatus: status as OCPP20ConnectorStatusEnumType,
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        evseId: evseId!
+        evseId: evseId!,
+        timestamp: new Date(),
       } satisfies OCPP20StatusNotificationRequest
     default:
       throw new BaseError('Cannot build status notification payload: OCPP version not supported')
@@ -161,7 +161,7 @@ const isIdTagRemoteAuthorized = async (
         chargingStation,
         RequestCommand.AUTHORIZE,
         {
-          idTag
+          idTag,
         }
       )
     ).idTagInfo.status === AuthorizationStatus.ACCEPTED
@@ -179,8 +179,8 @@ export const sendAndSetConnectorStatus = async (
   if (options.send) {
     checkConnectorStatusTransition(chargingStation, connectorId, status)
     await chargingStation.ocppRequestService.requestHandler<
-    StatusNotificationRequest,
-    StatusNotificationResponse
+      StatusNotificationRequest,
+      StatusNotificationResponse
     >(
       chargingStation,
       RequestCommand.STATUS_NOTIFICATION,
@@ -191,7 +191,7 @@ export const sendAndSetConnectorStatus = async (
   chargingStation.getConnectorStatus(connectorId)!.status = status
   chargingStation.emit(ChargingStationEvents.connectorStatusChanged, {
     connectorId,
-    ...chargingStation.getConnectorStatus(connectorId)
+    ...chargingStation.getConnectorStatus(connectorId),
   })
 }
 
@@ -249,6 +249,7 @@ const checkConnectorStatusTransition = (
       break
     default:
       throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         `Cannot check connector status transition: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
       )
   }
@@ -256,7 +257,8 @@ const checkConnectorStatusTransition = (
     logger.warn(
       `${chargingStation.logPrefix()} OCPP ${
         chargingStation.stationInfo.ocppVersion
-      } connector id ${connectorId} status transition from '${
+      } connector id ${connectorId.toString()} status transition from '${
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         chargingStation.getConnectorStatus(connectorId)?.status
       }' to '${status}' is not allowed`
     )
@@ -264,6 +266,38 @@ const checkConnectorStatusTransition = (
   return transitionAllowed
 }
 
+export const ajvErrorsToErrorType = (errors: ErrorObject[] | null | undefined): ErrorType => {
+  if (isNotEmptyArray(errors)) {
+    for (const error of errors) {
+      switch (error.keyword) {
+        case 'dependencies':
+        case 'required':
+          return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION
+        case 'format':
+        case 'pattern':
+          return ErrorType.PROPERTY_CONSTRAINT_VIOLATION
+        case 'type':
+          return ErrorType.TYPE_CONSTRAINT_VIOLATION
+      }
+    }
+  }
+  return ErrorType.FORMAT_VIOLATION
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export const convertDateToISOString = <T extends JsonType>(object: T): void => {
+  // eslint-disable-next-line @typescript-eslint/no-for-in-array
+  for (const key in object) {
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
+    if (isDate(object![key])) {
+      ;(object[key] as unknown as string) = (object[key] as Date).toISOString()
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
+    } else if (typeof object![key] === 'object' && object[key] !== null) {
+      convertDateToISOString<T>(object[key] as T)
+    }
+  }
+}
+
 export const buildMeterValue = (
   chargingStation: ChargingStation,
   connectorId: number,
@@ -273,6 +307,7 @@ export const buildMeterValue = (
 ): MeterValue => {
   const connector = chargingStation.getConnectorStatus(connectorId)
   let meterValue: MeterValue
+  let connectorMaximumAvailablePower: number | undefined
   let socSampledValueTemplate: SampledValueTemplate | undefined
   let voltageSampledValueTemplate: SampledValueTemplate | undefined
   let powerSampledValueTemplate: SampledValueTemplate | undefined
@@ -283,8 +318,8 @@ export const buildMeterValue = (
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16:
       meterValue = {
+        sampledValue: [],
         timestamp: new Date(),
-        sampledValue: []
       }
       // SoC measurand
       socSampledValueTemplate = getSampledValueTemplate(
@@ -297,7 +332,7 @@ export const buildMeterValue = (
         const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0
         const socSampledValueTemplateValue = isNotEmptyString(socSampledValueTemplate.value)
           ? getRandomFloatFluctuatedRounded(
-            parseInt(socSampledValueTemplate.value),
+            Number.parseInt(socSampledValueTemplate.value),
             socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
           : randomInt(socMinimumValue, socMaximumValue)
@@ -314,9 +349,10 @@ export const buildMeterValue = (
             `${chargingStation.logPrefix()} MeterValues measurand ${
               meterValue.sampledValue[sampledValuesIndex].measurand ??
               MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${socMinimumValue}/${
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${socMinimumValue.toString()}/${
               meterValue.sampledValue[sampledValuesIndex].value
-            }/${socMaximumValue}`
+            }/${socMaximumValue.toString()}`
           )
         }
       }
@@ -328,7 +364,7 @@ export const buildMeterValue = (
       )
       if (voltageSampledValueTemplate != null) {
         const voltageSampledValueTemplateValue = isNotEmptyString(voltageSampledValueTemplate.value)
-          ? parseInt(voltageSampledValueTemplate.value)
+          ? Number.parseInt(voltageSampledValueTemplate.value)
           : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
           chargingStation.stationInfo.voltageOut!
         const fluctuationPercent =
@@ -351,7 +387,7 @@ export const buildMeterValue = (
           chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
           phase++
         ) {
-          const phaseLineToNeutralValue = `L${phase}-N`
+          const phaseLineToNeutralValue = `L${phase.toString()}-N`
           const voltagePhaseLineToNeutralSampledValueTemplate = getSampledValueTemplate(
             chargingStation,
             connectorId,
@@ -363,7 +399,7 @@ export const buildMeterValue = (
             const voltagePhaseLineToNeutralSampledValueTemplateValue = isNotEmptyString(
               voltagePhaseLineToNeutralSampledValueTemplate.value
             )
-              ? parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
+              ? Number.parseInt(voltagePhaseLineToNeutralSampledValueTemplate.value)
               : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
               chargingStation.stationInfo.voltageOut!
             const fluctuationPhaseToNeutralPercent =
@@ -383,10 +419,10 @@ export const buildMeterValue = (
             )
           )
           if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) {
-            const phaseLineToLineValue = `L${phase}-L${
+            const phaseLineToLineValue = `L${phase.toString()}-L${
               (phase + 1) % chargingStation.getNumberOfPhases() !== 0
-                ? (phase + 1) % chargingStation.getNumberOfPhases()
-                : chargingStation.getNumberOfPhases()
+                ? ((phase + 1) % chargingStation.getNumberOfPhases()).toString()
+                : chargingStation.getNumberOfPhases().toString()
             }`
             const voltagePhaseLineToLineValueRounded = roundTo(
               Math.sqrt(chargingStation.getNumberOfPhases()) *
@@ -405,7 +441,7 @@ export const buildMeterValue = (
               const voltagePhaseLineToLineSampledValueTemplateValue = isNotEmptyString(
                 voltagePhaseLineToLineSampledValueTemplate.value
               )
-                ? parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
+                ? Number.parseInt(voltagePhaseLineToLineSampledValueTemplate.value)
                 : voltagePhaseLineToLineValueRounded
               const fluctuationPhaseLineToLinePercent =
                 voltagePhaseLineToLineSampledValueTemplate.fluctuationPercent ??
@@ -455,22 +491,22 @@ export const buildMeterValue = (
             connectorId,
             MeterValueMeasurand.POWER_ACTIVE_IMPORT,
             MeterValuePhase.L3_N
-          )
+          ),
         }
       }
       if (powerSampledValueTemplate != null) {
         checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand)
         const errMsg = `MeterValues measurand ${
           powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
           chargingStation.templateFile
         }, cannot calculate ${
           powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         } measurand value`
-        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
         const powerMeasurandValues: MeasurandValues = {} as MeasurandValues
         const unitDivider = powerSampledValueTemplate.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
-        const connectorMaximumAvailablePower =
+        connectorMaximumAvailablePower =
           chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
         const connectorMaximumPowerPerPhase = Math.round(
@@ -492,9 +528,9 @@ export const buildMeterValue = (
                     connectorMaximumPower / unitDivider,
                     connectorMinimumPower / unitDivider,
                     {
+                      fallbackValue: connectorMinimumPower / unitDivider,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPower / unitDivider
                     }
                   ) / chargingStation.getNumberOfPhases(),
                   powerSampledValueTemplate.fluctuationPercent ??
@@ -510,9 +546,9 @@ export const buildMeterValue = (
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
                   powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
@@ -528,9 +564,9 @@ export const buildMeterValue = (
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
                   powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
@@ -546,9 +582,9 @@ export const buildMeterValue = (
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
+                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
                   powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
@@ -584,9 +620,9 @@ export const buildMeterValue = (
                     connectorMaximumPower / unitDivider,
                     connectorMinimumPower / unitDivider,
                     {
+                      fallbackValue: connectorMinimumPower / unitDivider,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumPower / unitDivider
                     }
                   ),
                   powerSampledValueTemplate.fluctuationPercent ??
@@ -612,9 +648,9 @@ export const buildMeterValue = (
                   connectorMaximumPower / unitDivider,
                   connectorMinimumPower / unitDivider,
                   {
+                    fallbackValue: connectorMinimumPower / unitDivider,
                     limitationEnabled:
                         chargingStation.stationInfo.customValueLimitationMeterValues,
-                    fallbackValue: connectorMinimumPower / unitDivider
                   }
                 ),
                 powerSampledValueTemplate.fluctuationPercent ??
@@ -646,9 +682,10 @@ export const buildMeterValue = (
             `${chargingStation.logPrefix()} MeterValues measurand ${
               meterValue.sampledValue[sampledValuesIndex].measurand ??
               MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerRounded}/${
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPowerRounded.toString()}/${
               meterValue.sampledValue[sampledValuesIndex].value
-            }/${connectorMaximumPowerRounded}`
+            }/${connectorMaximumPowerRounded.toString()}`
           )
         }
         for (
@@ -656,13 +693,15 @@ export const buildMeterValue = (
           chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
           phase++
         ) {
-          const phaseValue = `L${phase}-N`
+          const phaseValue = `L${phase.toString()}-N`
           meterValue.sampledValue.push(
             buildSampledValue(
               powerPerPhaseSampledValueTemplates[
-                `L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates
+                `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
               ] ?? powerSampledValueTemplate,
-              powerMeasurandValues[`L${phase}` as keyof MeasurandPerPhaseSampledValueTemplates],
+              powerMeasurandValues[
+                `L${phase.toString()}` as keyof MeasurandPerPhaseSampledValueTemplates
+              ],
               undefined,
               phaseValue as MeterValuePhase
             )
@@ -688,10 +727,12 @@ export const buildMeterValue = (
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
                 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
               }: phase ${
+                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-              }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumPowerPerPhaseRounded}/${
+                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              }, connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPowerPerPhaseRounded.toString()}/${
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-              }/${connectorMaximumPowerPerPhaseRounded}`
+              }/${connectorMaximumPowerPerPhaseRounded.toString()}`
             )
           }
         }
@@ -721,23 +762,23 @@ export const buildMeterValue = (
             connectorId,
             MeterValueMeasurand.CURRENT_IMPORT,
             MeterValuePhase.L3
-          )
+          ),
         }
       }
       if (currentSampledValueTemplate != null) {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand)
         const errMsg = `MeterValues measurand ${
           currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
           chargingStation.templateFile
         }, cannot calculate ${
           currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         } measurand value`
-        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
         const currentMeasurandValues: MeasurandValues = {} as MeasurandValues
-        const connectorMaximumAvailablePower =
-          chargingStation.getConnectorMaximumAvailablePower(connectorId)
+        connectorMaximumAvailablePower == null &&
+          (connectorMaximumAvailablePower =
+            chargingStation.getConnectorMaximumAvailablePower(connectorId))
         const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0
         let connectorMaximumAmperage: number
         switch (chargingStation.stationInfo.currentOutType) {
@@ -758,9 +799,9 @@ export const buildMeterValue = (
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
+                      fallbackValue: connectorMinimumAmperage,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage
                     }
                   ),
                   currentSampledValueTemplate.fluctuationPercent ??
@@ -776,9 +817,9 @@ export const buildMeterValue = (
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
+                      fallbackValue: connectorMinimumAmperage,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage
                     }
                   ),
                   currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
@@ -794,9 +835,9 @@ export const buildMeterValue = (
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
+                      fallbackValue: connectorMinimumAmperage,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage
                     }
                   ),
                   currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
@@ -812,9 +853,9 @@ export const buildMeterValue = (
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
+                      fallbackValue: connectorMinimumAmperage,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage
                     }
                   ),
                   currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
@@ -841,9 +882,9 @@ export const buildMeterValue = (
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
+                      fallbackValue: connectorMinimumAmperage,
                       limitationEnabled:
                           chargingStation.stationInfo.customValueLimitationMeterValues,
-                      fallbackValue: connectorMinimumAmperage
                     }
                   ),
                   currentSampledValueTemplate.fluctuationPercent ??
@@ -872,9 +913,9 @@ export const buildMeterValue = (
                   connectorMaximumAmperage,
                   connectorMinimumAmperage,
                   {
+                    fallbackValue: connectorMinimumAmperage,
                     limitationEnabled:
                         chargingStation.stationInfo.customValueLimitationMeterValues,
-                    fallbackValue: connectorMinimumAmperage
                   }
                 ),
                 currentSampledValueTemplate.fluctuationPercent ??
@@ -901,9 +942,10 @@ export const buildMeterValue = (
             `${chargingStation.logPrefix()} MeterValues measurand ${
               meterValue.sampledValue[sampledValuesIndex].measurand ??
               MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${
               meterValue.sampledValue[sampledValuesIndex].value
-            }/${connectorMaximumAmperage}`
+            }/${connectorMaximumAmperage.toString()}`
           )
         }
         for (
@@ -911,7 +953,7 @@ export const buildMeterValue = (
           chargingStation.getNumberOfPhases() === 3 && phase <= chargingStation.getNumberOfPhases();
           phase++
         ) {
-          const phaseValue = `L${phase}`
+          const phaseValue = `L${phase.toString()}`
           meterValue.sampledValue.push(
             buildSampledValue(
               currentPerPhaseSampledValueTemplates[
@@ -935,10 +977,12 @@ export const buildMeterValue = (
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ??
                 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
               }: phase ${
+                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
-              }, connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumAmperage}/${
+              }, connector id ${connectorId.toString()}, transaction id $
+              connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${
                 meterValue.sampledValue[sampledValuesPerPhaseIndex].value
-              }/${connectorMaximumAmperage}`
+              }/${connectorMaximumAmperage.toString()}`
             )
           }
         }
@@ -949,8 +993,9 @@ export const buildMeterValue = (
         checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand)
         const unitDivider =
           energySampledValueTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
-        const connectorMaximumAvailablePower =
-          chargingStation.getConnectorMaximumAvailablePower(connectorId)
+        connectorMaximumAvailablePower == null &&
+          (connectorMaximumAvailablePower =
+            chargingStation.getConnectorMaximumAvailablePower(connectorId))
         const connectorMaximumEnergyRounded = roundTo(
           (connectorMaximumAvailablePower * interval) / (3600 * 1000),
           2
@@ -966,9 +1011,9 @@ export const buildMeterValue = (
               connectorMaximumEnergyRounded,
               connectorMinimumEnergyRounded,
               {
-                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
                 fallbackValue: connectorMinimumEnergyRounded,
-                unitMultiplier: unitDivider
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
+                unitMultiplier: unitDivider,
               }
             ),
             energySampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
@@ -1009,7 +1054,8 @@ export const buildMeterValue = (
             `${chargingStation.logPrefix()} MeterValues measurand ${
               meterValue.sampledValue[sampledValuesIndex].measurand ??
               MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-            }: connector id ${connectorId}, transaction id ${connector?.transactionId}, value: ${connectorMinimumEnergyRounded}/${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${interval}ms`
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+            }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumEnergyRounded.toString()}/${energyValueRounded.toString()}/${connectorMaximumEnergyRounded.toString()}, duration: ${interval.toString()}ms`
           )
         }
       }
@@ -1018,6 +1064,7 @@ export const buildMeterValue = (
     case OCPPVersion.VERSION_201:
     default:
       throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
       )
   }
@@ -1034,8 +1081,8 @@ export const buildTransactionEndMeterValue = (
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16:
       meterValue = {
+        sampledValue: [],
         timestamp: new Date(),
-        sampledValue: []
       }
       // Energy.Active.Import.Register measurand (default)
       sampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId)
@@ -1053,6 +1100,7 @@ export const buildTransactionEndMeterValue = (
     case OCPPVersion.VERSION_201:
     default:
       throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         `Cannot build meterValue: OCPP version ${chargingStation.stationInfo?.ocppVersion} not supported`
       )
   }
@@ -1071,7 +1119,7 @@ const checkMeasurandPowerDivider = (
   } else if (chargingStation.powerDivider <= 0) {
     const errMsg = `MeterValues measurand ${
       measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-    }: powerDivider have zero or below value ${chargingStation.powerDivider}`
+    }: powerDivider have zero or below value ${chargingStation.powerDivider.toString()}`
     logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
     throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
   }
@@ -1081,26 +1129,36 @@ const getLimitFromSampledValueTemplateCustomValue = (
   value: string | undefined,
   maxLimit: number,
   minLimit: number,
-  options?: { limitationEnabled?: boolean, fallbackValue?: number, unitMultiplier?: number }
+  options?: {
+    fallbackValue?: number
+    limitationEnabled?: boolean
+    unitMultiplier?: number
+  }
 ): number => {
   options = {
     ...{
+      fallbackValue: 0,
       limitationEnabled: false,
       unitMultiplier: 1,
-      fallbackValue: 0
     },
-    ...options
+    ...options,
   }
-  const parsedValue = parseInt(value ?? '')
-  if (options.limitationEnabled === true) {
+  const parsedValue = Number.parseInt(value ?? '')
+  if (options.limitationEnabled) {
     return max(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit),
+      min(
+        (!Number.isNaN(parsedValue) ? parsedValue : Number.POSITIVE_INFINITY) *
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          options.unitMultiplier!,
+        maxLimit
+      ),
       minLimit
     )
   }
-  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-  return (!isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!
+  return (
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    (!Number.isNaN(parsedValue) ? parsedValue : options.fallbackValue!) * options.unitMultiplier!
+  )
 }
 
 const getSampledValueTemplate = (
@@ -1112,7 +1170,7 @@ const getSampledValueTemplate = (
   const onPhaseStr = phase != null ? `on phase ${phase} ` : ''
   if (!OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(measurand)) {
     logger.warn(
-      `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
+      `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId.toString()}`
     )
     return
   }
@@ -1124,7 +1182,7 @@ const getSampledValueTemplate = (
     )?.value?.includes(measurand) === false
   ) {
     logger.debug(
-      `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId} not found in '${
+      `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId.toString()} not found in '${
         StandardParametersKey.MeterValuesSampledData
       }' OCPP parameter`
     )
@@ -1140,16 +1198,16 @@ const getSampledValueTemplate = (
   ) {
     if (
       !OCPPConstants.OCPP_MEASURANDS_SUPPORTED.includes(
-        sampledValueTemplates[index]?.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
+        sampledValueTemplates[index].measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       )
     ) {
       logger.warn(
-        `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
+        `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId.toString()}`
       )
     } else if (
       phase != null &&
-      sampledValueTemplates[index]?.phase === phase &&
-      sampledValueTemplates[index]?.measurand === measurand &&
+      sampledValueTemplates[index].phase === phase &&
+      sampledValueTemplates[index].measurand === measurand &&
       getConfigurationKey(
         chargingStation,
         StandardParametersKey.MeterValuesSampledData
@@ -1158,8 +1216,8 @@ const getSampledValueTemplate = (
       return sampledValueTemplates[index]
     } else if (
       phase == null &&
-      sampledValueTemplates[index]?.phase == null &&
-      sampledValueTemplates[index]?.measurand === measurand &&
+      sampledValueTemplates[index].phase == null &&
+      sampledValueTemplates[index].measurand === measurand &&
       getConfigurationKey(
         chargingStation,
         StandardParametersKey.MeterValuesSampledData
@@ -1168,19 +1226,19 @@ const getSampledValueTemplate = (
       return sampledValueTemplates[index]
     } else if (
       measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
-      (sampledValueTemplates[index]?.measurand == null ||
-        sampledValueTemplates[index]?.measurand === measurand)
+      (sampledValueTemplates[index].measurand == null ||
+        sampledValueTemplates[index].measurand === measurand)
     ) {
       return sampledValueTemplates[index]
     }
   }
   if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
-    const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId}`
+    const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connector id ${connectorId.toString()}`
     logger.error(`${chargingStation.logPrefix()} ${errorMsg}`)
     throw new BaseError(errorMsg)
   }
   logger.debug(
-    `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId}`
+    `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connector id ${connectorId.toString()}`
   )
 }
 
@@ -1197,15 +1255,15 @@ const buildSampledValue = (
   const sampledValuePhase = phase ?? sampledValueTemplate.phase
   return {
     ...(sampledValueTemplate.unit != null && {
-      unit: sampledValueTemplate.unit
+      unit: sampledValueTemplate.unit,
     }),
     ...(sampledValueContext != null && { context: sampledValueContext }),
     ...(sampledValueTemplate.measurand != null && {
-      measurand: sampledValueTemplate.measurand
+      measurand: sampledValueTemplate.measurand,
     }),
     ...(sampledValueLocation != null && { location: sampledValueLocation }),
     ...{ value: value.toString() },
-    ...(sampledValuePhase != null && { phase: sampledValuePhase })
+    ...(sampledValuePhase != null && { phase: sampledValuePhase }),
   } satisfies SampledValue
 }
 
@@ -1242,54 +1300,30 @@ const getMeasurandDefaultLocation = (
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPServiceUtils {
-  public static readonly getMessageTypeString = getMessageTypeString
-  public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus
-  public static readonly restoreConnectorStatus = restoreConnectorStatus
-  public static readonly isIdTagAuthorized = isIdTagAuthorized
   public static readonly buildTransactionEndMeterValue = buildTransactionEndMeterValue
-  protected static getSampledValueTemplate = getSampledValueTemplate
+  public static readonly isIdTagAuthorized = isIdTagAuthorized
+  public static readonly restoreConnectorStatus = restoreConnectorStatus
+  public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus
+
   protected static buildSampledValue = buildSampledValue
+  protected static getSampledValueTemplate = getSampledValueTemplate
 
   protected constructor () {
     // This is intentional
   }
 
-  public static ajvErrorsToErrorType (errors: ErrorObject[] | undefined | null): ErrorType {
-    if (isNotEmptyArray(errors)) {
-      for (const error of errors as DefinedError[]) {
-        switch (error.keyword) {
-          case 'type':
-            return ErrorType.TYPE_CONSTRAINT_VIOLATION
-          case 'dependencies':
-          case 'required':
-            return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION
-          case 'pattern':
-          case 'format':
-            return ErrorType.PROPERTY_CONSTRAINT_VIOLATION
-        }
-      }
-    }
-    return ErrorType.FORMAT_VIOLATION
-  }
-
-  public static isRequestCommandSupported (
+  public static isConnectorIdValid (
     chargingStation: ChargingStation,
-    command: RequestCommand
+    ocppCommand: IncomingRequestCommand,
+    connectorId: number
   ): boolean {
-    const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command)
-    if (
-      isRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.outgoingCommands == null
-    ) {
-      return true
-    } else if (
-      isRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] != null
-    ) {
-      return chargingStation.stationInfo.commandsSupport.outgoingCommands[command]
+    if (connectorId < 0) {
+      logger.error(
+        `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId.toString()}`
+      )
+      return false
     }
-    logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
-    return false
+    return true
   }
 
   public static isIncomingRequestCommandSupported (
@@ -1332,40 +1366,24 @@ export class OCPPServiceUtils {
     return false
   }
 
-  public static isConnectorIdValid (
+  public static isRequestCommandSupported (
     chargingStation: ChargingStation,
-    ocppCommand: IncomingRequestCommand,
-    connectorId: number
+    command: RequestCommand
   ): boolean {
-    if (connectorId < 0) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${ocppCommand} incoming request received with invalid connector id ${connectorId}`
-      )
-      return false
-    }
-    return true
-  }
-
-  public static convertDateToISOString<T extends JsonType>(object: T): void {
-    for (const key in object) {
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-      if (isDate(object![key])) {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-        (object![key] as string) = (object![key] as Date).toISOString()
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
-      } else if (typeof object![key] === 'object' && object![key] !== null) {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-        OCPPServiceUtils.convertDateToISOString<T>(object![key] as T)
-      }
-    }
-  }
-
-  public static startHeartbeatInterval (chargingStation: ChargingStation, interval: number): void {
-    if (chargingStation.heartbeatSetInterval == null) {
-      chargingStation.startHeartbeat()
-    } else if (chargingStation.getHeartbeatInterval() !== interval) {
-      chargingStation.restartHeartbeat()
+    const isRequestCommand = Object.values<RequestCommand>(RequestCommand).includes(command)
+    if (
+      isRequestCommand &&
+      chargingStation.stationInfo?.commandsSupport?.outgoingCommands == null
+    ) {
+      return true
+    } else if (
+      isRequestCommand &&
+      chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] != null
+    ) {
+      return chargingStation.stationInfo.commandsSupport.outgoingCommands[command]
     }
+    logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
+    return false
   }
 
   protected static parseJsonSchemaFile<T extends JsonType>(
@@ -1385,7 +1403,6 @@ export class OCPPServiceUtils {
         OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName),
         { throwError: false }
       )
-      // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
       return {} as JSONSchemaType<T>
     }
   }
index e4e565c34196d0078e9641151964e08b1a17e8a0..6357eaaa83ebec846d1ba53dcd7260a2839a6625 100644 (file)
@@ -11,5 +11,5 @@ export {
   buildTransactionEndMeterValue,
   getMessageTypeString,
   isIdTagAuthorized,
-  sendAndSetConnectorStatus
+  sendAndSetConnectorStatus,
 } from './OCPPServiceUtils.js'
index 9d2705f28f677f37ffcc869c60587ade090745f5..9068d4745d4f503a6e37301abf5ba5373c1bdf1b 100644 (file)
@@ -1,7 +1,9 @@
+import type { WebSocket } from 'ws'
+
 import { type IncomingMessage, Server, type ServerResponse } from 'node:http'
 import { createServer, type Http2Server } from 'node:http2'
 
-import type { WebSocket } from 'ws'
+import type { AbstractUIService } from './ui-services/AbstractUIService.js'
 
 import { BaseError } from '../../exception/index.js'
 import {
@@ -15,10 +17,9 @@ import {
   ProtocolVersion,
   type RequestPayload,
   type ResponsePayload,
-  type UIServerConfiguration
+  type UIServerConfiguration,
 } from '../../types/index.js'
 import { logger } from '../../utils/index.js'
-import type { AbstractUIService } from './ui-services/AbstractUIService.js'
 import { UIServiceFactory } from './ui-services/UIServiceFactory.js'
 import { getUsernameAndPasswordFromAuthorizationToken } from './UIServerUtils.js'
 
@@ -27,10 +28,11 @@ const moduleName = 'AbstractUIServer'
 export abstract class AbstractUIServer {
   public readonly chargingStations: Map<string, ChargingStationData>
   public readonly chargingStationTemplates: Set<string>
-  protected readonly httpServer: Server | Http2Server
+
+  protected readonly httpServer: Http2Server | Server
   protected readonly responseHandlers: Map<
     `${string}-${string}-${string}-${string}-${string}`,
-  ServerResponse | WebSocket
+    ServerResponse | WebSocket
   >
 
   protected readonly uiServices: Map<ProtocolVersion, AbstractUIService>
@@ -47,12 +49,13 @@ export abstract class AbstractUIServer {
         break
       default:
         throw new BaseError(
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
           `Unsupported application protocol version ${this.uiServerConfiguration.version} in '${ConfigurationSection.uiServer}' configuration section`
         )
     }
     this.responseHandlers = new Map<
       `${string}-${string}-${string}-${string}-${string}`,
-    ServerResponse | WebSocket
+      ServerResponse | WebSocket
     >()
     this.uiServices = new Map<ProtocolVersion, AbstractUIService>()
   }
@@ -72,19 +75,17 @@ export abstract class AbstractUIServer {
     return [uuid, responsePayload]
   }
 
-  public stop (): void {
-    this.stopHttpServer()
-    for (const uiService of this.uiServices.values()) {
-      uiService.stop()
-    }
-    this.clearCaches()
-  }
-
   public clearCaches (): void {
     this.chargingStations.clear()
     this.chargingStationTemplates.clear()
   }
 
+  public hasResponseHandler (uuid: `${string}-${string}-${string}-${string}-${string}`): boolean {
+    return this.responseHandlers.has(uuid)
+  }
+
+  public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
+
   public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
     const protocolVersion = ProtocolVersion['0.0.1']
     this.registerProtocolVersionUIService(protocolVersion)
@@ -93,26 +94,18 @@ export abstract class AbstractUIServer {
       ?.requestHandler(request) as Promise<ProtocolResponse>)
   }
 
-  public hasResponseHandler (uuid: `${string}-${string}-${string}-${string}-${string}`): boolean {
-    return this.responseHandlers.has(uuid)
-  }
+  public abstract sendRequest (request: ProtocolRequest): void
 
-  protected startHttpServer (): void {
-    this.httpServer.on('error', error => {
-      logger.error(
-        `${this.logPrefix(moduleName, 'start.httpServer.on.error')} HTTP server error:`,
-        error
-      )
-    })
-    if (!this.httpServer.listening) {
-      this.httpServer.listen(this.uiServerConfiguration.options)
-    }
-  }
+  public abstract sendResponse (response: ProtocolResponse): void
 
-  protected registerProtocolVersionUIService (version: ProtocolVersion): void {
-    if (!this.uiServices.has(version)) {
-      this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this))
+  public abstract start (): void
+
+  public stop (): void {
+    this.stopHttpServer()
+    for (const uiService of this.uiServices.values()) {
+      uiService.stop()
     }
+    this.clearCaches()
   }
 
   protected authenticate (req: IncomingMessage, next: (err?: Error) => void): void {
@@ -133,10 +126,21 @@ export abstract class AbstractUIServer {
     next()
   }
 
-  private stopHttpServer (): void {
-    if (this.httpServer.listening) {
-      this.httpServer.close()
-      this.httpServer.removeAllListeners()
+  protected registerProtocolVersionUIService (version: ProtocolVersion): void {
+    if (!this.uiServices.has(version)) {
+      this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this))
+    }
+  }
+
+  protected startHttpServer (): void {
+    this.httpServer.on('error', error => {
+      logger.error(
+        `${this.logPrefix(moduleName, 'start.httpServer.on.error')} HTTP server error:`,
+        error
+      )
+    })
+    if (!this.httpServer.listening) {
+      this.httpServer.listen(this.uiServerConfiguration.options)
     }
   }
 
@@ -165,8 +169,10 @@ export abstract class AbstractUIServer {
   private isValidProtocolBasicAuth (req: IncomingMessage, next: (err?: Error) => void): boolean {
     const authorizationProtocol = req.headers['sec-websocket-protocol']?.split(/,\s+/).pop()
     const [username, password] = getUsernameAndPasswordFromAuthorizationToken(
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      `${authorizationProtocol}${Array(((4 - (authorizationProtocol!.length % 4)) % 4) + 1).join('=')}`
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/restrict-template-expressions
+      `${authorizationProtocol}${Array(((4 - (authorizationProtocol!.length % 4)) % 4) + 1).join(
+        '='
+      )}`
         .split('.')
         .pop() ?? '',
       next
@@ -181,8 +187,10 @@ export abstract class AbstractUIServer {
     )
   }
 
-  public abstract start (): void
-  public abstract sendRequest (request: ProtocolRequest): void
-  public abstract sendResponse (response: ProtocolResponse): void
-  public abstract logPrefix (moduleName?: string, methodName?: string, prefixSuffix?: string): string
+  private stopHttpServer (): void {
+    if (this.httpServer.listening) {
+      this.httpServer.close()
+      this.httpServer.removeAllListeners()
+    }
+  }
 }
index 5bcae7beff6ed84fc6d0e202437361bc7f1c3725..b1357822a2045790ebba995a6bc9d1a7d6631f31 100644 (file)
@@ -13,7 +13,7 @@ import {
   type ProtocolVersion,
   type RequestPayload,
   ResponseStatus,
-  type UIServerConfiguration
+  type UIServerConfiguration,
 } from '../../types/index.js'
 import {
   Constants,
@@ -21,7 +21,7 @@ import {
   isNotEmptyString,
   JSONStringify,
   logger,
-  logPrefix
+  logPrefix,
 } from '../../utils/index.js'
 import { AbstractUIServer } from './AbstractUIServer.js'
 import { isProtocolAndVersionSupported } from './UIServerUtils.js'
@@ -30,19 +30,23 @@ const moduleName = 'UIHttpServer'
 
 enum HttpMethods {
   GET = 'GET',
-  PUT = 'PUT',
+  PATCH = 'PATCH',
   POST = 'POST',
-  PATCH = 'PATCH'
+  PUT = 'PUT',
 }
 
 export class UIHttpServer extends AbstractUIServer {
-  public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
+  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
     super(uiServerConfiguration)
   }
 
-  public start (): void {
-    this.httpServer.on('request', this.requestListener.bind(this))
-    this.startHttpServer()
+  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
+    const logMsgPrefix = prefixSuffix != null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
+    const logMsg =
+      isNotEmptyString(modName) && isNotEmptyString(methodName)
+        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+        : ` ${logMsgPrefix} |`
+    return logPrefix(logMsg)
   }
 
   public sendRequest (request: ProtocolRequest): void {
@@ -60,7 +64,7 @@ export class UIHttpServer extends AbstractUIServer {
         const res = this.responseHandlers.get(uuid) as ServerResponse
         res
           .writeHead(this.responseStatusToStatusCode(payload.status), {
-            'Content-Type': 'application/json'
+            'Content-Type': 'application/json',
           })
           .end(JSONStringify(payload, undefined, MapStringifyFormat.object))
       } else {
@@ -78,13 +82,9 @@ export class UIHttpServer extends AbstractUIServer {
     }
   }
 
-  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
-    const logMsgPrefix = prefixSuffix != null ? `UI HTTP Server ${prefixSuffix}` : 'UI HTTP Server'
-    const logMsg =
-      isNotEmptyString(modName) && isNotEmptyString(methodName)
-        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
-        : ` ${logMsgPrefix} |`
-    return logPrefix(logMsg)
+  public start (): void {
+    this.httpServer.on('request', this.requestListener.bind(this))
+    this.startHttpServer()
   }
 
   private requestListener (req: IncomingMessage, res: ServerResponse): void {
@@ -93,9 +93,9 @@ export class UIHttpServer extends AbstractUIServer {
         res
           .writeHead(StatusCodes.UNAUTHORIZED, {
             'Content-Type': 'text/plain',
-            'WWW-Authenticate': 'Basic realm=users'
+            'WWW-Authenticate': 'Basic realm=users',
           })
-          .end(`${StatusCodes.UNAUTHORIZED} Unauthorized`)
+          .end(`${StatusCodes.UNAUTHORIZED.toString()} Unauthorized`)
         res.destroy()
         req.destroy()
       }
@@ -133,9 +133,9 @@ export class UIHttpServer extends AbstractUIServer {
             } catch (error) {
               this.sendResponse(
                 this.buildProtocolResponse(uuid, {
-                  status: ResponseStatus.FAILURE,
                   errorMessage: (error as Error).message,
-                  errorStack: (error as Error).stack
+                  errorStack: (error as Error).stack,
+                  status: ResponseStatus.FAILURE,
                 })
               )
               return
@@ -147,10 +147,12 @@ export class UIHttpServer extends AbstractUIServer {
                 if (protocolResponse != null) {
                   this.sendResponse(protocolResponse)
                 }
+                return undefined
               })
               .catch(Constants.EMPTY_FUNCTION)
           })
       } else {
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         throw new BaseError(`Unsupported HTTP method: '${req.method}'`)
       }
     } catch (error) {
@@ -164,10 +166,10 @@ export class UIHttpServer extends AbstractUIServer {
 
   private responseStatusToStatusCode (status: ResponseStatus): StatusCodes {
     switch (status) {
-      case ResponseStatus.SUCCESS:
-        return StatusCodes.OK
       case ResponseStatus.FAILURE:
         return StatusCodes.BAD_REQUEST
+      case ResponseStatus.SUCCESS:
+        return StatusCodes.OK
       default:
         return StatusCodes.INTERNAL_SERVER_ERROR
     }
index 34138e02b0204636b79d5ab884fe9f6328514d8f..3bdf68f6641688ec55dee124e9fee0abc2a6c182 100644 (file)
@@ -1,15 +1,16 @@
 import chalk from 'chalk'
 
+import type { AbstractUIServer } from './AbstractUIServer.js'
+
 import { BaseError } from '../../exception/index.js'
 import {
   ApplicationProtocol,
   ApplicationProtocolVersion,
   AuthenticationType,
   ConfigurationSection,
-  type UIServerConfiguration
+  type UIServerConfiguration,
 } from '../../types/index.js'
 import { logger, logPrefix } from '../../utils/index.js'
-import type { AbstractUIServer } from './AbstractUIServer.js'
 import { UIHttpServer } from './UIHttpServer.js'
 import { isLoopback } from './UIServerUtils.js'
 import { UIWebSocketServer } from './UIWebSocketServer.js'
@@ -68,8 +69,11 @@ export class UIServerFactory {
             uiServerConfiguration.type as ApplicationProtocol
           )
         ) {
-          // eslint-disable-next-line @typescript-eslint/no-base-to-string
-          const logMsg = `Unknown application protocol type '${uiServerConfiguration.type}' in '${ConfigurationSection.uiServer}' configuration section from values '${ApplicationProtocol.toString()}', defaulting to '${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          const logMsg = `Unknown application protocol type '${uiServerConfiguration.type}' in '${
+            ConfigurationSection.uiServer
+            // eslint-disable-next-line @typescript-eslint/no-base-to-string
+          }' configuration section from values '${ApplicationProtocol.toString()}', defaulting to '${
             ApplicationProtocol.WS
           }'`
           logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`)
index cfe4fd2f6198ee6ae544d0b46b786e9f496f69af..419c9f63646a5a2216dde19677f88bb519b34e19 100644 (file)
@@ -2,7 +2,7 @@ import type { IncomingMessage } from 'node:http'
 
 import { BaseError } from '../../exception/index.js'
 import { Protocol, ProtocolVersion } from '../../types/index.js'
-import { logger, logPrefix } from '../../utils/index.js'
+import { isEmpty, logger, logPrefix } from '../../utils/index.js'
 
 export const getUsernameAndPasswordFromAuthorizationToken = (
   authorizationToken: string,
@@ -22,10 +22,10 @@ export const getUsernameAndPasswordFromAuthorizationToken = (
 export const handleProtocols = (
   protocols: Set<string>,
   _request: IncomingMessage
-): string | false => {
+): false | string => {
   let protocol: Protocol | undefined
   let version: ProtocolVersion | undefined
-  if (protocols.size === 0) {
+  if (isEmpty(protocols)) {
     return false
   }
   for (const fullProtocol of protocols) {
@@ -36,6 +36,7 @@ export const handleProtocols = (
   logger.error(
     `${logPrefix(
       ' UI WebSocket Server |'
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
     )} Unsupported protocol: '${protocol}' or protocol version: '${version}'`
   )
   return false
@@ -59,6 +60,5 @@ export const getProtocolAndVersion = (protocolStr: string): [Protocol, ProtocolV
 }
 
 export const isLoopback = (address: string): boolean => {
-  // eslint-disable-next-line no-useless-escape
-  return /^localhost$|^127(?:\.\d+){0,2}\.\d+$|^(?:0*\:)*?:?0*1$/i.test(address)
+  return /^localhost$|^127(?:\.\d+){0,2}\.\d+$|^(?:0*:)*?:?0*1$/i.test(address)
 }
index bf0a6c444749201fe06d2cdcc6f24b10fa09582a..dd57883e88b1bbe3273452ab3a43d0b3b7222608 100644 (file)
@@ -9,7 +9,7 @@ import {
   type ProtocolRequest,
   type ProtocolResponse,
   type UIServerConfiguration,
-  WebSocketCloseEventStatusCode
+  WebSocketCloseEventStatusCode,
 } from '../../types/index.js'
 import {
   Constants,
@@ -18,13 +18,13 @@ import {
   JSONStringify,
   logger,
   logPrefix,
-  validateUUID
+  validateUUID,
 } from '../../utils/index.js'
 import { AbstractUIServer } from './AbstractUIServer.js'
 import {
   getProtocolAndVersion,
   handleProtocols,
-  isProtocolAndVersionSupported
+  isProtocolAndVersionSupported,
 } from './UIServerUtils.js'
 
 const moduleName = 'UIWebSocketServer'
@@ -32,14 +32,64 @@ const moduleName = 'UIWebSocketServer'
 export class UIWebSocketServer extends AbstractUIServer {
   private readonly webSocketServer: WebSocketServer
 
-  public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) {
+  public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) {
     super(uiServerConfiguration)
     this.webSocketServer = new WebSocketServer({
       handleProtocols,
-      noServer: true
+      noServer: true,
     })
   }
 
+  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
+    const logMsgPrefix =
+      prefixSuffix != null ? `UI WebSocket Server ${prefixSuffix}` : 'UI WebSocket Server'
+    const logMsg =
+      isNotEmptyString(modName) && isNotEmptyString(methodName)
+        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
+        : ` ${logMsgPrefix} |`
+    return logPrefix(logMsg)
+  }
+
+  public sendRequest (request: ProtocolRequest): void {
+    this.broadcastToClients(JSON.stringify(request))
+  }
+
+  public sendResponse (response: ProtocolResponse): void {
+    const responseId = response[0]
+    try {
+      if (this.hasResponseHandler(responseId)) {
+        const ws = this.responseHandlers.get(responseId) as WebSocket
+        if (ws.readyState === WebSocket.OPEN) {
+          ws.send(JSONStringify(response, undefined, MapStringifyFormat.object))
+        } else {
+          logger.error(
+            `${this.logPrefix(
+              moduleName,
+              'sendResponse'
+            )} Error at sending response id '${responseId}', WebSocket is not open: ${ws.readyState.toString()}`
+          )
+        }
+      } else {
+        logger.error(
+          `${this.logPrefix(
+            moduleName,
+            'sendResponse'
+          )} Response for unknown request id: ${responseId}`
+        )
+      }
+    } catch (error) {
+      logger.error(
+        `${this.logPrefix(
+          moduleName,
+          'sendResponse'
+        )} Error at sending response id '${responseId}':`,
+        error
+      )
+    } finally {
+      this.responseHandlers.delete(responseId)
+    }
+  }
+
   public start (): void {
     this.webSocketServer.on('connection', (ws: WebSocket, _req: IncomingMessage): void => {
       if (!isProtocolAndVersionSupported(ws.protocol)) {
@@ -68,6 +118,7 @@ export class UIWebSocketServer extends AbstractUIServer {
             if (protocolResponse != null) {
               this.sendResponse(protocolResponse)
             }
+            return undefined
           })
           .catch(Constants.EMPTY_FUNCTION)
       })
@@ -87,7 +138,7 @@ export class UIWebSocketServer extends AbstractUIServer {
     })
     this.httpServer.on('connect', (req: IncomingMessage, socket: Duplex, _head: Buffer) => {
       if (req.headers.connection !== 'Upgrade' || req.headers.upgrade !== 'websocket') {
-        socket.write(`HTTP/1.1 ${StatusCodes.BAD_REQUEST} Bad Request\r\n\r\n`)
+        socket.write(`HTTP/1.1 ${StatusCodes.BAD_REQUEST.toString()} Bad Request\r\n\r\n`)
         socket.destroy()
       }
     })
@@ -104,7 +155,7 @@ export class UIWebSocketServer extends AbstractUIServer {
       socket.on('error', onSocketError)
       this.authenticate(req, err => {
         if (err != null) {
-          socket.write(`HTTP/1.1 ${StatusCodes.UNAUTHORIZED} Unauthorized\r\n\r\n`)
+          socket.write(`HTTP/1.1 ${StatusCodes.UNAUTHORIZED.toString()} Unauthorized\r\n\r\n`)
           socket.destroy()
           return
         }
@@ -127,58 +178,6 @@ export class UIWebSocketServer extends AbstractUIServer {
     this.startHttpServer()
   }
 
-  public sendRequest (request: ProtocolRequest): void {
-    this.broadcastToClients(JSON.stringify(request))
-  }
-
-  public sendResponse (response: ProtocolResponse): void {
-    const responseId = response[0]
-    try {
-      if (this.hasResponseHandler(responseId)) {
-        const ws = this.responseHandlers.get(responseId) as WebSocket
-        if (ws.readyState === WebSocket.OPEN) {
-          ws.send(JSONStringify(response, undefined, MapStringifyFormat.object))
-        } else {
-          logger.error(
-            `${this.logPrefix(
-              moduleName,
-              'sendResponse'
-            )} Error at sending response id '${responseId}', WebSocket is not open: ${
-              ws.readyState
-            }`
-          )
-        }
-      } else {
-        logger.error(
-          `${this.logPrefix(
-            moduleName,
-            'sendResponse'
-          )} Response for unknown request id: ${responseId}`
-        )
-      }
-    } catch (error) {
-      logger.error(
-        `${this.logPrefix(
-          moduleName,
-          'sendResponse'
-        )} Error at sending response id '${responseId}':`,
-        error
-      )
-    } finally {
-      this.responseHandlers.delete(responseId)
-    }
-  }
-
-  public logPrefix = (modName?: string, methodName?: string, prefixSuffix?: string): string => {
-    const logMsgPrefix =
-      prefixSuffix != null ? `UI WebSocket Server ${prefixSuffix}` : 'UI WebSocket Server'
-    const logMsg =
-      isNotEmptyString(modName) && isNotEmptyString(methodName)
-        ? ` ${logMsgPrefix} | ${modName}.${methodName}:`
-        : ` ${logMsgPrefix} |`
-    return logPrefix(logMsg)
-  }
-
   private broadcastToClients (message: string): void {
     for (const client of this.webSocketServer.clients) {
       if (client.readyState === WebSocket.OPEN) {
@@ -187,7 +186,7 @@ export class UIWebSocketServer extends AbstractUIServer {
     }
   }
 
-  private validateRawDataRequest (rawData: RawData): ProtocolRequest | false {
+  private validateRawDataRequest (rawData: RawData): false | ProtocolRequest {
     // logger.debug(
     //   `${this.logPrefix(
     //     moduleName,
index ffdc256583adccd581d72c99496e3bfe726333e2..0ca837e888353e16e88e5bc5cdb20170eda02234 100644 (file)
@@ -1,3 +1,5 @@
+import type { AbstractUIServer } from '../AbstractUIServer.js'
+
 import { BaseError, type OCPPError } from '../../../exception/index.js'
 import {
   BroadcastChannelProcedureName,
@@ -15,92 +17,104 @@ import {
   type RequestPayload,
   type ResponsePayload,
   ResponseStatus,
-  type StorageConfiguration
+  type StorageConfiguration,
 } from '../../../types/index.js'
 import { Configuration, isAsyncFunction, isNotEmptyArray, logger } from '../../../utils/index.js'
 import { Bootstrap } from '../../Bootstrap.js'
 import { UIServiceWorkerBroadcastChannel } from '../../broadcast-channel/UIServiceWorkerBroadcastChannel.js'
-import type { AbstractUIServer } from '../AbstractUIServer.js'
 
 const moduleName = 'AbstractUIService'
 
 interface AddChargingStationsRequestPayload extends RequestPayload {
-  template: string
   numberOfStations: number
   options?: ChargingStationOptions
+  template: string
 }
 
 export abstract class AbstractUIService {
   protected static readonly ProcedureNameToBroadCastChannelProcedureNameMapping = new Map<
-  ProcedureName,
-  BroadcastChannelProcedureName
+    ProcedureName,
+    BroadcastChannelProcedureName
   >([
-    [ProcedureName.START_CHARGING_STATION, BroadcastChannelProcedureName.START_CHARGING_STATION],
-    [ProcedureName.STOP_CHARGING_STATION, BroadcastChannelProcedureName.STOP_CHARGING_STATION],
+    [ProcedureName.AUTHORIZE, BroadcastChannelProcedureName.AUTHORIZE],
+    [ProcedureName.BOOT_NOTIFICATION, BroadcastChannelProcedureName.BOOT_NOTIFICATION],
+    [ProcedureName.CLOSE_CONNECTION, BroadcastChannelProcedureName.CLOSE_CONNECTION],
+    [ProcedureName.DATA_TRANSFER, BroadcastChannelProcedureName.DATA_TRANSFER],
     [
       ProcedureName.DELETE_CHARGING_STATIONS,
-      BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS
+      BroadcastChannelProcedureName.DELETE_CHARGING_STATIONS,
     ],
-    [ProcedureName.CLOSE_CONNECTION, BroadcastChannelProcedureName.CLOSE_CONNECTION],
-    [ProcedureName.OPEN_CONNECTION, BroadcastChannelProcedureName.OPEN_CONNECTION],
     [
-      ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
-      BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR
+      ProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
+      BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
     ],
     [
-      ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
-      BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR
+      ProcedureName.FIRMWARE_STATUS_NOTIFICATION,
+      BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
     ],
-    [ProcedureName.SET_SUPERVISION_URL, BroadcastChannelProcedureName.SET_SUPERVISION_URL],
-    [ProcedureName.START_TRANSACTION, BroadcastChannelProcedureName.START_TRANSACTION],
-    [ProcedureName.STOP_TRANSACTION, BroadcastChannelProcedureName.STOP_TRANSACTION],
-    [ProcedureName.AUTHORIZE, BroadcastChannelProcedureName.AUTHORIZE],
-    [ProcedureName.BOOT_NOTIFICATION, BroadcastChannelProcedureName.BOOT_NOTIFICATION],
-    [ProcedureName.STATUS_NOTIFICATION, BroadcastChannelProcedureName.STATUS_NOTIFICATION],
     [ProcedureName.HEARTBEAT, BroadcastChannelProcedureName.HEARTBEAT],
     [ProcedureName.METER_VALUES, BroadcastChannelProcedureName.METER_VALUES],
-    [ProcedureName.DATA_TRANSFER, BroadcastChannelProcedureName.DATA_TRANSFER],
+    [ProcedureName.OPEN_CONNECTION, BroadcastChannelProcedureName.OPEN_CONNECTION],
+    [ProcedureName.SET_SUPERVISION_URL, BroadcastChannelProcedureName.SET_SUPERVISION_URL],
     [
-      ProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
-      BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION
+      ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
+      BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
     ],
+    [ProcedureName.START_CHARGING_STATION, BroadcastChannelProcedureName.START_CHARGING_STATION],
+    [ProcedureName.START_TRANSACTION, BroadcastChannelProcedureName.START_TRANSACTION],
+    [ProcedureName.STATUS_NOTIFICATION, BroadcastChannelProcedureName.STATUS_NOTIFICATION],
     [
-      ProcedureName.FIRMWARE_STATUS_NOTIFICATION,
-      BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION
-    ]
+      ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
+      BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
+    ],
+    [ProcedureName.STOP_CHARGING_STATION, BroadcastChannelProcedureName.STOP_CHARGING_STATION],
+    [ProcedureName.STOP_TRANSACTION, BroadcastChannelProcedureName.STOP_TRANSACTION],
   ])
 
   protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>
-  private readonly version: ProtocolVersion
-  private readonly uiServer: AbstractUIServer
-  private readonly uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel
   private readonly broadcastChannelRequests: Map<
     `${string}-${string}-${string}-${string}-${string}`,
-  number
+    number
   >
 
+  private readonly uiServer: AbstractUIServer
+  private readonly uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel
+  private readonly version: ProtocolVersion
+
   constructor (uiServer: AbstractUIServer, version: ProtocolVersion) {
     this.uiServer = uiServer
     this.version = version
     this.requestHandlers = new Map<ProcedureName, ProtocolRequestHandler>([
-      [ProcedureName.LIST_TEMPLATES, this.handleListTemplates.bind(this)],
-      [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
       [ProcedureName.ADD_CHARGING_STATIONS, this.handleAddChargingStations.bind(this)],
+      [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
+      [ProcedureName.LIST_TEMPLATES, this.handleListTemplates.bind(this)],
       [ProcedureName.PERFORMANCE_STATISTICS, this.handlePerformanceStatistics.bind(this)],
       [ProcedureName.SIMULATOR_STATE, this.handleSimulatorState.bind(this)],
       [ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)],
-      [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)]
+      [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)],
     ])
     this.uiServiceWorkerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this)
     this.broadcastChannelRequests = new Map<
       `${string}-${string}-${string}-${string}-${string}`,
-    number
+      number
     >()
   }
 
-  public stop (): void {
-    this.broadcastChannelRequests.clear()
-    this.uiServiceWorkerBroadcastChannel.close()
+  public deleteBroadcastChannelRequest (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): void {
+    this.broadcastChannelRequests.delete(uuid)
+  }
+
+  public getBroadcastChannelExpectedResponses (
+    uuid: `${string}-${string}-${string}-${string}-${string}`
+  ): number {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return this.broadcastChannelRequests.get(uuid)!
+  }
+
+  public logPrefix = (modName: string, methodName: string): string => {
+    return this.uiServer.logPrefix(modName, methodName, this.version)
   }
 
   public async requestHandler (request: ProtocolRequest): Promise<ProtocolResponse | undefined> {
@@ -109,7 +123,7 @@ export abstract class AbstractUIService {
     let requestPayload: RequestPayload | undefined
     let responsePayload: ResponsePayload | undefined
     try {
-      [uuid, command, requestPayload] = request
+      ;[uuid, command, requestPayload] = request
 
       if (!this.requestHandlers.has(command)) {
         throw new BaseError(
@@ -132,21 +146,21 @@ export abstract class AbstractUIService {
             uuid?: string,
             procedureName?: ProcedureName,
             payload?: RequestPayload
-          ) => undefined | ResponsePayload
+          ) => ResponsePayload | undefined
         )(uuid, command, requestPayload)
       }
     } catch (error) {
       // Log
       logger.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error)
       responsePayload = {
-        hashIds: requestPayload?.hashIds,
-        status: ResponseStatus.FAILURE,
         command,
-        requestPayload,
-        responsePayload,
+        errorDetails: (error as OCPPError).details,
         errorMessage: (error as OCPPError).message,
         errorStack: (error as OCPPError).stack,
-        errorDetails: (error as OCPPError).details
+        hashIds: requestPayload?.hashIds,
+        requestPayload,
+        responsePayload,
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
     if (responsePayload != null) {
@@ -155,16 +169,6 @@ export abstract class AbstractUIService {
     }
   }
 
-  // public sendRequest (
-  //   uuid: `${string}-${string}-${string}-${string}-${string}`,
-  //   procedureName: ProcedureName,
-  //   requestPayload: RequestPayload
-  // ): void {
-  //   this.uiServer.sendRequest(
-  //     this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
-  //   )
-  // }
-
   public sendResponse (
     uuid: `${string}-${string}-${string}-${string}-${string}`,
     responsePayload: ResponsePayload
@@ -174,21 +178,9 @@ export abstract class AbstractUIService {
     }
   }
 
-  public logPrefix = (modName: string, methodName: string): string => {
-    return this.uiServer.logPrefix(modName, methodName, this.version)
-  }
-
-  public deleteBroadcastChannelRequest (
-    uuid: `${string}-${string}-${string}-${string}-${string}`
-  ): void {
-    this.broadcastChannelRequests.delete(uuid)
-  }
-
-  public getBroadcastChannelExpectedResponses (
-    uuid: `${string}-${string}-${string}-${string}-${string}`
-  ): number {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.broadcastChannelRequests.get(uuid)!
+  public stop (): void {
+    this.broadcastChannelRequests.clear()
+    this.uiServiceWorkerBroadcastChannel.close()
   }
 
   protected handleProtocolRequest (
@@ -204,79 +196,30 @@ export abstract class AbstractUIService {
     )
   }
 
-  private sendBroadcastChannelRequest (
-    uuid: `${string}-${string}-${string}-${string}-${string}`,
-    procedureName: BroadcastChannelProcedureName,
-    payload: BroadcastChannelRequestPayload
-  ): void {
-    if (isNotEmptyArray(payload.hashIds)) {
-      payload.hashIds = payload.hashIds
-        .map(hashId => {
-          if (this.uiServer.chargingStations.has(hashId)) {
-            return hashId
-          }
-          logger.warn(
-            `${this.logPrefix(
-              moduleName,
-              'sendBroadcastChannelRequest'
-            )} Charging station with hashId '${hashId}' not found`
-          )
-          return undefined
-        })
-        .filter(hashId => hashId != null) as string[]
-    } else {
-      delete payload.hashIds
-    }
-    const expectedNumberOfResponses = Array.isArray(payload.hashIds)
-      ? payload.hashIds.length
-      : this.uiServer.chargingStations.size
-    if (expectedNumberOfResponses === 0) {
-      throw new BaseError(
-        'hashIds array in the request payload does not contain any valid charging station hashId'
-      )
-    }
-    this.uiServiceWorkerBroadcastChannel.sendRequest([uuid, procedureName, payload])
-    this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses)
-  }
-
-  private handleListTemplates (): ResponsePayload {
-    return {
-      status: ResponseStatus.SUCCESS,
-      templates: [...this.uiServer.chargingStationTemplates.values()] as JsonType[]
-    } satisfies ResponsePayload
-  }
-
-  private handleListChargingStations (): ResponsePayload {
-    return {
-      status: ResponseStatus.SUCCESS,
-      chargingStations: [...this.uiServer.chargingStations.values()] as JsonType[]
-    } satisfies ResponsePayload
-  }
-
   private async handleAddChargingStations (
     _uuid?: `${string}-${string}-${string}-${string}-${string}`,
     _procedureName?: ProcedureName,
     requestPayload?: RequestPayload
   ): Promise<ResponsePayload> {
-    const { template, numberOfStations, options } =
+    const { numberOfStations, options, template } =
       requestPayload as AddChargingStationsRequestPayload
     if (!Bootstrap.getInstance().getState().started) {
       return {
-        status: ResponseStatus.FAILURE,
         errorMessage:
-          'Cannot add charging station(s) while the charging stations simulator is not started'
+          'Cannot add charging station(s) while the charging stations simulator is not started',
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
     if (typeof template !== 'string' || typeof numberOfStations !== 'number') {
       return {
+        errorMessage: 'Invalid request payload',
         status: ResponseStatus.FAILURE,
-        errorMessage: 'Invalid request payload'
       } satisfies ResponsePayload
     }
     if (!this.uiServer.chargingStationTemplates.has(template)) {
       return {
+        errorMessage: `Template '${template}' not found`,
         status: ResponseStatus.FAILURE,
-        errorMessage: `Template '${template}' not found`
       } satisfies ResponsePayload
     }
     const succeededStationInfos: ChargingStationInfo[] = []
@@ -303,12 +246,26 @@ export abstract class AbstractUIService {
     return {
       status: err != null ? ResponseStatus.FAILURE : ResponseStatus.SUCCESS,
       ...(succeededStationInfos.length > 0 && {
-        hashIdsSucceeded: succeededStationInfos.map(stationInfo => stationInfo.hashId)
+        hashIdsSucceeded: succeededStationInfos.map(stationInfo => stationInfo.hashId),
       }),
       ...(failedStationInfos.length > 0 && {
-        hashIdsFailed: failedStationInfos.map(stationInfo => stationInfo.hashId)
+        hashIdsFailed: failedStationInfos.map(stationInfo => stationInfo.hashId),
       }),
-      ...(err != null && { errorMessage: err.message, errorStack: err.stack })
+      ...(err != null && { errorMessage: err.message, errorStack: err.stack }),
+    } satisfies ResponsePayload
+  }
+
+  private handleListChargingStations (): ResponsePayload {
+    return {
+      chargingStations: [...this.uiServer.chargingStations.values()] as JsonType[],
+      status: ResponseStatus.SUCCESS,
+    } satisfies ResponsePayload
+  }
+
+  private handleListTemplates (): ResponsePayload {
+    return {
+      status: ResponseStatus.SUCCESS,
+      templates: [...this.uiServer.chargingStationTemplates.values()],
     } satisfies ResponsePayload
   }
 
@@ -319,23 +276,23 @@ export abstract class AbstractUIService {
       ).enabled !== true
     ) {
       return {
+        errorMessage: 'Performance statistics storage is not enabled',
         status: ResponseStatus.FAILURE,
-        errorMessage: 'Performance statistics storage is not enabled'
       } satisfies ResponsePayload
     }
     try {
       return {
-        status: ResponseStatus.SUCCESS,
         performanceStatistics: [
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ...Bootstrap.getInstance().getPerformanceStatistics()!
-        ] as JsonType[]
+          ...Bootstrap.getInstance().getPerformanceStatistics()!,
+        ] as JsonType[],
+        status: ResponseStatus.SUCCESS,
       } satisfies ResponsePayload
     } catch (error) {
       return {
-        status: ResponseStatus.FAILURE,
         errorMessage: (error as Error).message,
-        errorStack: (error as Error).stack
+        errorStack: (error as Error).stack,
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
   }
@@ -343,14 +300,14 @@ export abstract class AbstractUIService {
   private handleSimulatorState (): ResponsePayload {
     try {
       return {
+        state: Bootstrap.getInstance().getState() as unknown as JsonObject,
         status: ResponseStatus.SUCCESS,
-        state: Bootstrap.getInstance().getState() as unknown as JsonObject
       } satisfies ResponsePayload
     } catch (error) {
       return {
-        status: ResponseStatus.FAILURE,
         errorMessage: (error as Error).message,
-        errorStack: (error as Error).stack
+        errorStack: (error as Error).stack,
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
   }
@@ -361,23 +318,68 @@ export abstract class AbstractUIService {
       return { status: ResponseStatus.SUCCESS }
     } catch (error) {
       return {
-        status: ResponseStatus.FAILURE,
         errorMessage: (error as Error).message,
-        errorStack: (error as Error).stack
+        errorStack: (error as Error).stack,
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
   }
 
+  // public sendRequest (
+  //   uuid: `${string}-${string}-${string}-${string}-${string}`,
+  //   procedureName: ProcedureName,
+  //   requestPayload: RequestPayload
+  // ): void {
+  //   this.uiServer.sendRequest(
+  //     this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
+  //   )
+  // }
+
   private async handleStopSimulator (): Promise<ResponsePayload> {
     try {
       await Bootstrap.getInstance().stop()
       return { status: ResponseStatus.SUCCESS }
     } catch (error) {
       return {
-        status: ResponseStatus.FAILURE,
         errorMessage: (error as Error).message,
-        errorStack: (error as Error).stack
+        errorStack: (error as Error).stack,
+        status: ResponseStatus.FAILURE,
       } satisfies ResponsePayload
     }
   }
+
+  private sendBroadcastChannelRequest (
+    uuid: `${string}-${string}-${string}-${string}-${string}`,
+    procedureName: BroadcastChannelProcedureName,
+    payload: BroadcastChannelRequestPayload
+  ): void {
+    if (isNotEmptyArray(payload.hashIds)) {
+      payload.hashIds = payload.hashIds
+        .map(hashId => {
+          if (this.uiServer.chargingStations.has(hashId)) {
+            return hashId
+          }
+          logger.warn(
+            `${this.logPrefix(
+              moduleName,
+              'sendBroadcastChannelRequest'
+            )} Charging station with hashId '${hashId}' not found`
+          )
+          return undefined
+        })
+        .filter(hashId => hashId != null)
+    } else {
+      delete payload.hashIds
+    }
+    const expectedNumberOfResponses = Array.isArray(payload.hashIds)
+      ? payload.hashIds.length
+      : this.uiServer.chargingStations.size
+    if (expectedNumberOfResponses === 0) {
+      throw new BaseError(
+        'hashIds array in the request payload does not contain any valid charging station hashId'
+      )
+    }
+    this.uiServiceWorkerBroadcastChannel.sendRequest([uuid, procedureName, payload])
+    this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses)
+  }
 }
index 511bfa7a3de64383c7302584a5c4c6f42d155157..515536696704a4753ad5d8c3560c5ea07e4cc377 100644 (file)
@@ -1,5 +1,6 @@
-import { type ProtocolRequestHandler, ProtocolVersion } from '../../../types/index.js'
 import type { AbstractUIServer } from '../AbstractUIServer.js'
+
+import { type ProtocolRequestHandler, ProtocolVersion } from '../../../types/index.js'
 import { AbstractUIService } from './AbstractUIService.js'
 
 export class UIService001 extends AbstractUIService {
index 08d81a360bdc669dac2d6fc2fd009d09a8e306bd..607d6e2fe08cd05f261f41b9a6e6fa0700ec4cca 100644 (file)
@@ -1,6 +1,7 @@
-import { ProtocolVersion } from '../../../types/index.js'
 import type { AbstractUIServer } from '../AbstractUIServer.js'
 import type { AbstractUIService } from './AbstractUIService.js'
+
+import { ProtocolVersion } from '../../../types/index.js'
 import { UIService001 } from './UIService001.js'
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
@@ -14,6 +15,7 @@ export class UIServiceFactory {
     uiServer: AbstractUIServer
   ): AbstractUIService {
     switch (version) {
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
       case ProtocolVersion['0.0.1']:
         return new UIService001(uiServer)
     }
index 1e716ae4d1d8f7c530e489a7fb37151f5fba8644..ece0283b81218d2bb95f5683dbed26c3bf843a5f 100644 (file)
@@ -1,18 +1,19 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
 import type { ErrorType, IncomingRequestCommand, JsonType, RequestCommand } from '../types/index.js'
+
 import { Constants } from '../utils/index.js'
 import { BaseError } from './BaseError.js'
 
 export class OCPPError extends BaseError {
   code: ErrorType
-  command: RequestCommand | IncomingRequestCommand
+  command: IncomingRequestCommand | RequestCommand
   details?: JsonType
 
   constructor (
     code: ErrorType,
     message: string,
-    command?: RequestCommand | IncomingRequestCommand,
+    command?: IncomingRequestCommand | RequestCommand,
     details?: JsonType
   ) {
     super(message)
index f05a4182324f51725be663140d649cdd1bfb8d11..6d862f69a6338172833d7c0e14481bb779eb23b0 100644 (file)
@@ -1,12 +1,11 @@
 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
-import { performance, type PerformanceEntry, PerformanceObserver } from 'node:perf_hooks'
 import type { URL } from 'node:url'
-import { parentPort } from 'node:worker_threads'
 
 import { secondsToMilliseconds } from 'date-fns'
 import { CircularBuffer } from 'mnemonist'
-import { is, mean, median } from 'rambda'
+import { performance, type PerformanceEntry, PerformanceObserver } from 'node:perf_hooks'
+import { parentPort } from 'node:worker_threads'
 
 import { BaseError } from '../exception/index.js'
 import {
@@ -19,9 +18,10 @@ import {
   type Statistics,
   type StatisticsData,
   type StorageConfiguration,
-  type TimestampedData
+  type TimestampedData,
 } from '../types/index.js'
 import {
+  average,
   buildPerformanceStatisticsMessage,
   Configuration,
   Constants,
@@ -32,40 +32,70 @@ import {
   logger,
   logPrefix,
   max,
+  median,
   min,
-  nthPercentile,
-  stdDeviation
+  percentile,
+  std,
 } from '../utils/index.js'
 
 export class PerformanceStatistics {
   private static readonly instances: Map<string, PerformanceStatistics> = new Map<
-  string,
-  PerformanceStatistics
+    string,
+    PerformanceStatistics
   >()
 
+  private displayInterval?: NodeJS.Timeout
   private readonly objId: string | undefined
   private readonly objName: string | undefined
   private performanceObserver!: PerformanceObserver
   private readonly statistics: Statistics
-  private displayInterval?: NodeJS.Timeout
 
   private constructor (objId: string, objName: string, uri: URL) {
     this.objId = objId
     this.objName = objName
     this.initializePerformanceObserver()
     this.statistics = {
+      createdAt: new Date(),
       id: this.objId,
       name: this.objName,
+      statisticsData: new Map(),
       uri: uri.toString(),
-      createdAt: new Date(),
-      statisticsData: new Map()
     }
   }
 
+  public static beginMeasure (id: string): string {
+    const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}`
+    performance.mark(markId)
+    return markId
+  }
+
+  public static deleteInstance (objId: string | undefined): boolean {
+    if (objId == null) {
+      const errMsg = 'Cannot delete performance statistics instance without specifying object id'
+      logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
+      throw new BaseError(errMsg)
+    }
+    return PerformanceStatistics.instances.delete(objId)
+  }
+
+  public static endMeasure (name: string, markId: string): void {
+    try {
+      performance.measure(name, markId)
+    } catch (error) {
+      if (error instanceof Error && error.message.includes('performance mark has not been set')) {
+        /* Ignore */
+      } else {
+        throw error
+      }
+    }
+    performance.clearMarks(markId)
+    performance.clearMeasures(name)
+  }
+
   public static getInstance (
     objId: string | undefined,
     objName: string | undefined,
-    uri: URL | undefined
+    uri: undefined | URL
   ): PerformanceStatistics | undefined {
     if (objId == null) {
       const errMsg = 'Cannot get performance statistics instance without specifying object id'
@@ -88,79 +118,54 @@ export class PerformanceStatistics {
     return PerformanceStatistics.instances.get(objId)
   }
 
-  public static deleteInstance (objId: string | undefined): boolean {
-    if (objId == null) {
-      const errMsg = 'Cannot delete performance statistics instance without specifying object id'
-      logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
-      throw new BaseError(errMsg)
-    }
-    return PerformanceStatistics.instances.delete(objId)
-  }
-
-  public static beginMeasure (id: string): string {
-    const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}`
-    performance.mark(markId)
-    return markId
-  }
-
-  public static endMeasure (name: string, markId: string): void {
-    try {
-      performance.measure(name, markId)
-    } catch (error) {
-      if (is(Error, error) && error.message.includes('performance mark has not been set')) {
-        /* Ignore */
-      } else {
-        throw error
-      }
-    }
-    performance.clearMarks(markId)
-    performance.clearMeasures(name)
+  private static readonly logPrefix = (): string => {
+    return logPrefix(' Performance statistics')
   }
 
   public addRequestStatistic (
-    command: RequestCommand | IncomingRequestCommand,
+    command: IncomingRequestCommand | RequestCommand,
     messageType: MessageType
   ): void {
     switch (messageType) {
-      case MessageType.CALL_MESSAGE:
+      case MessageType.CALL_ERROR_MESSAGE:
         if (
           this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.requestCount != null
+          this.statistics.statisticsData.get(command)?.errorCount != null
         ) {
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.requestCount!
+          ++this.statistics.statisticsData.get(command)!.errorCount!
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
-            requestCount: 1
+            errorCount: 1,
           })
         }
         break
-      case MessageType.CALL_RESULT_MESSAGE:
+      case MessageType.CALL_MESSAGE:
         if (
           this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.responseCount != null
+          this.statistics.statisticsData.get(command)?.requestCount != null
         ) {
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.responseCount!
+          ++this.statistics.statisticsData.get(command)!.requestCount!
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
-            responseCount: 1
+            requestCount: 1,
           })
         }
         break
-      case MessageType.CALL_ERROR_MESSAGE:
+      case MessageType.CALL_RESULT_MESSAGE:
         if (
           this.statistics.statisticsData.has(command) &&
-          this.statistics.statisticsData.get(command)?.errorCount != null
+          this.statistics.statisticsData.get(command)?.responseCount != null
         ) {
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          ++this.statistics.statisticsData.get(command)!.errorCount!
+          ++this.statistics.statisticsData.get(command)!.responseCount!
         } else {
           this.statistics.statisticsData.set(command, {
             ...this.statistics.statisticsData.get(command),
-            errorCount: 1
+            responseCount: 1,
           })
         }
         break
@@ -171,6 +176,11 @@ export class PerformanceStatistics {
     }
   }
 
+  public restart (): void {
+    this.stop()
+    this.start()
+  }
+
   public start (): void {
     this.startLogStatisticsInterval()
     const performanceStorageConfiguration =
@@ -179,9 +189,11 @@ export class PerformanceStatistics {
       )
     if (performanceStorageConfiguration.enabled === true) {
       logger.info(
-        `${this.logPrefix()} storage enabled: type ${performanceStorageConfiguration.type}, uri: ${
-          performanceStorageConfiguration.uri
-        }`
+        `${this.logPrefix()} storage enabled: type ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          performanceStorageConfiguration.type
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        }, uri: ${performanceStorageConfiguration.uri}`
       )
     }
   }
@@ -193,66 +205,6 @@ export class PerformanceStatistics {
     this.performanceObserver.disconnect()
   }
 
-  public restart (): void {
-    this.stop()
-    this.start()
-  }
-
-  private initializePerformanceObserver (): void {
-    this.performanceObserver = new PerformanceObserver(performanceObserverList => {
-      const lastPerformanceEntry = performanceObserverList.getEntries()[0]
-      // logger.debug(
-      //   `${this.logPrefix()} '${lastPerformanceEntry.name}' performance entry: %j`,
-      //   lastPerformanceEntry
-      // )
-      this.addPerformanceEntryToStatistics(lastPerformanceEntry)
-    })
-    this.performanceObserver.observe({ entryTypes: ['measure'] })
-  }
-
-  private logStatistics (): void {
-    logger.info(this.logPrefix(), {
-      ...this.statistics,
-      statisticsData: JSON.parse(
-        JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object)
-      ) as Map<string | RequestCommand | IncomingRequestCommand, StatisticsData>
-    })
-  }
-
-  private startLogStatisticsInterval (): void {
-    const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
-      ConfigurationSection.log
-    )
-    const logStatisticsInterval =
-      logConfiguration.enabled === true
-        ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        logConfiguration.statisticsInterval!
-        : 0
-    if (logStatisticsInterval > 0 && this.displayInterval == null) {
-      this.displayInterval = setInterval(() => {
-        this.logStatistics()
-      }, secondsToMilliseconds(logStatisticsInterval))
-      logger.info(
-        `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}`
-      )
-    } else if (this.displayInterval != null) {
-      logger.info(
-        `${this.logPrefix()} already logged every ${formatDurationSeconds(logStatisticsInterval)}`
-      )
-    } else if (logConfiguration.enabled === true) {
-      logger.info(
-        `${this.logPrefix()} log interval is set to ${logStatisticsInterval}. Not logging statistics`
-      )
-    }
-  }
-
-  private stopLogStatisticsInterval (): void {
-    if (this.displayInterval != null) {
-      clearInterval(this.displayInterval)
-      delete this.displayInterval
-    }
-  }
-
   private addPerformanceEntryToStatistics (entry: PerformanceEntry): void {
     // Initialize command statistics
     if (!this.statistics.statisticsData.has(entry.name)) {
@@ -290,23 +242,26 @@ export class PerformanceStatistics {
           Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY
         )
     }
-    this.statistics.statisticsData
-      .get(entry.name)
-      ?.measurementTimeSeries?.push({ timestamp: entry.startTime, value: entry.duration })
+    this.statistics.statisticsData.get(entry.name)?.measurementTimeSeries?.push({
+      timestamp: entry.startTime,
+      value: entry.duration,
+    })
     const timeMeasurementValues = extractTimeSeriesValues(
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries!
+      this.statistics.statisticsData.get(entry.name)!
+        .measurementTimeSeries as CircularBuffer<TimestampedData>
     )
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement = mean(timeMeasurementValues)
+    this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement =
+      average(timeMeasurementValues)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     this.statistics.statisticsData.get(entry.name)!.medTimeMeasurement =
       median(timeMeasurementValues)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     this.statistics.statisticsData.get(entry.name)!.ninetyFiveThPercentileTimeMeasurement =
-      nthPercentile(timeMeasurementValues, 95)
+      percentile(timeMeasurementValues, 95)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.statistics.statisticsData.get(entry.name)!.stdDevTimeMeasurement = stdDeviation(
+    this.statistics.statisticsData.get(entry.name)!.stdTimeMeasurement = std(
       timeMeasurementValues,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement
@@ -321,11 +276,63 @@ export class PerformanceStatistics {
     }
   }
 
-  private static readonly logPrefix = (): string => {
-    return logPrefix(' Performance statistics')
+  private initializePerformanceObserver (): void {
+    this.performanceObserver = new PerformanceObserver(performanceObserverList => {
+      const lastPerformanceEntry = performanceObserverList.getEntries()[0]
+      // logger.debug(
+      //   `${this.logPrefix()} '${lastPerformanceEntry.name}' performance entry: %j`,
+      //   lastPerformanceEntry
+      // )
+      this.addPerformanceEntryToStatistics(lastPerformanceEntry)
+    })
+    this.performanceObserver.observe({ entryTypes: ['measure'] })
   }
 
   private readonly logPrefix = (): string => {
+    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
     return logPrefix(` ${this.objName} | Performance statistics`)
   }
+
+  private logStatistics (): void {
+    logger.info(this.logPrefix(), {
+      ...this.statistics,
+      statisticsData: JSON.parse(
+        JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object)
+      ) as Map<IncomingRequestCommand | RequestCommand | string, StatisticsData>,
+    })
+  }
+
+  private startLogStatisticsInterval (): void {
+    const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
+      ConfigurationSection.log
+    )
+    const logStatisticsInterval =
+      logConfiguration.enabled === true
+        ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        logConfiguration.statisticsInterval!
+        : 0
+    if (logStatisticsInterval > 0 && this.displayInterval == null) {
+      this.displayInterval = setInterval(() => {
+        this.logStatistics()
+      }, secondsToMilliseconds(logStatisticsInterval))
+      logger.info(
+        `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}`
+      )
+    } else if (this.displayInterval != null) {
+      logger.info(
+        `${this.logPrefix()} already logged every ${formatDurationSeconds(logStatisticsInterval)}`
+      )
+    } else if (logConfiguration.enabled === true) {
+      logger.info(
+        `${this.logPrefix()} log interval is set to ${logStatisticsInterval.toString()}. Not logging statistics`
+      )
+    }
+  }
+
+  private stopLogStatisticsInterval (): void {
+    if (this.displayInterval != null) {
+      clearInterval(this.displayInterval)
+      delete this.displayInterval
+    }
+  }
 }
index 97ab104ccfb73a11fb52aafd8ca408eeaf1ff84f..bc1d45383ee9649b7f57e8bf796725cf9ac65a3c 100644 (file)
@@ -16,25 +16,21 @@ export class JsonFileStorage extends Storage {
     this.dbName = this.storageUri.pathname
   }
 
-  public storePerformanceStatistics (performanceStatistics: Statistics): void {
-    this.setPerformanceStatistics(performanceStatistics)
-    this.checkPerformanceRecordsFile()
-    AsyncLock.runExclusive(AsyncLockType.performance, () => {
-      writeSync(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.fd!,
-        JSONStringify([...this.getPerformanceStatistics()], 2, MapStringifyFormat.object),
-        0,
-        'utf8'
-      )
-    }).catch((error: unknown) => {
+  public close (): void {
+    this.clearPerformanceStatistics()
+    try {
+      if (this.fd != null) {
+        closeSync(this.fd)
+        delete this.fd
+      }
+    } catch (error) {
       handleFileException(
         this.dbName,
         FileType.PerformanceRecords,
         error as NodeJS.ErrnoException,
         this.logPrefix
       )
-    })
+    }
   }
 
   public open (): void {
@@ -55,21 +51,25 @@ export class JsonFileStorage extends Storage {
     }
   }
 
-  public close (): void {
-    this.clearPerformanceStatistics()
-    try {
-      if (this.fd != null) {
-        closeSync(this.fd)
-        delete this.fd
-      }
-    } catch (error) {
+  public storePerformanceStatistics (performanceStatistics: Statistics): void {
+    this.setPerformanceStatistics(performanceStatistics)
+    this.checkPerformanceRecordsFile()
+    AsyncLock.runExclusive(AsyncLockType.performance, () => {
+      writeSync(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.fd!,
+        JSONStringify([...this.getPerformanceStatistics()], 2, MapStringifyFormat.object),
+        0,
+        'utf8'
+      )
+    }).catch((error: unknown) => {
       handleFileException(
         this.dbName,
         FileType.PerformanceRecords,
         error as NodeJS.ErrnoException,
         this.logPrefix
       )
-    }
+    })
   }
 
   private checkPerformanceRecordsFile (): void {
index 7b4545bdff36ba824597c22549889641c6f60538..f9998a0d71797a119548af3f14014bc16b205faf 100644 (file)
@@ -1,15 +1,15 @@
 // Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
-import { MikroORM as MariaDbORM, type Options as MariaDbOptions } from '@mikro-orm/mariadb'
-import { MikroORM as SqliteORM, type Options as SqliteOptions } from '@mikro-orm/sqlite'
+import { type Options as MariaDbOptions, MikroORM as MariaDbORM } from '@mikro-orm/mariadb'
+import { type Options as SqliteOptions, MikroORM as SqliteORM } from '@mikro-orm/sqlite'
 
 import { type PerformanceRecord, type Statistics, StorageType } from '../../types/index.js'
 import { Constants } from '../../utils/index.js'
 import { Storage } from './Storage.js'
 
 export class MikroOrmStorage extends Storage {
+  private orm?: MariaDbORM | SqliteORM
   private readonly storageType: StorageType
-  private orm?: SqliteORM | MariaDbORM
 
   constructor (storageUri: string, logPrefix: string, storageType: StorageType) {
     super(storageUri, logPrefix)
@@ -17,18 +17,15 @@ export class MikroOrmStorage extends Storage {
     this.dbName = this.getDBName()
   }
 
-  public async storePerformanceStatistics (performanceStatistics: Statistics): Promise<void> {
+  public async close (): Promise<void> {
+    this.clearPerformanceStatistics()
     try {
-      this.setPerformanceStatistics(performanceStatistics)
-      await this.orm?.em.upsert({
-        ...performanceStatistics,
-        statisticsData: Array.from(performanceStatistics.statisticsData, ([name, value]) => ({
-          name,
-          ...value
-        }))
-      } satisfies PerformanceRecord)
+      if (this.orm != null) {
+        await this.orm.close()
+        delete this.orm
+      }
     } catch (error) {
-      this.handleDBError(this.storageType, error as Error, Constants.PERFORMANCE_RECORDS_TABLE)
+      this.handleDBStorageError(this.storageType, error as Error)
     }
   }
 
@@ -36,29 +33,45 @@ export class MikroOrmStorage extends Storage {
     try {
       if (this.orm == null) {
         switch (this.storageType) {
-          case StorageType.SQLITE:
-            this.orm = await SqliteORM.init(this.getOptions() as SqliteOptions)
-            break
           case StorageType.MARIA_DB:
           case StorageType.MYSQL:
             this.orm = await MariaDbORM.init(this.getOptions() as MariaDbOptions)
             break
+          case StorageType.SQLITE:
+            this.orm = await SqliteORM.init(this.getOptions() as SqliteOptions)
+            break
         }
       }
     } catch (error) {
-      this.handleDBError(this.storageType, error as Error)
+      this.handleDBStorageError(this.storageType, error as Error)
     }
   }
 
-  public async close (): Promise<void> {
-    this.clearPerformanceStatistics()
+  public async storePerformanceStatistics (performanceStatistics: Statistics): Promise<void> {
     try {
-      if (this.orm != null) {
-        await this.orm.close()
-        delete this.orm
-      }
+      this.setPerformanceStatistics(performanceStatistics)
+      await this.orm?.em.upsert({
+        ...performanceStatistics,
+        statisticsData: Array.from(performanceStatistics.statisticsData, ([name, value]) => ({
+          name,
+          ...value,
+        })),
+      } satisfies PerformanceRecord)
     } catch (error) {
-      this.handleDBError(this.storageType, error as Error)
+      this.handleDBStorageError(
+        this.storageType,
+        error as Error,
+        Constants.PERFORMANCE_RECORDS_TABLE
+      )
+    }
+  }
+
+  private getClientUrl (): string | undefined {
+    switch (this.storageType) {
+      case StorageType.MARIA_DB:
+      case StorageType.MYSQL:
+      case StorageType.SQLITE:
+        return this.storageUri.toString()
     }
   }
 
@@ -69,21 +82,12 @@ export class MikroOrmStorage extends Storage {
     return this.storageUri.pathname.replace(/(?:^\/)|(?:\/$)/g, '')
   }
 
-  private getOptions (): SqliteOptions | MariaDbOptions {
+  private getOptions (): MariaDbOptions | SqliteOptions {
     return {
+      clientUrl: this.getClientUrl(),
       dbName: this.dbName,
       entities: ['./dist/types/orm/entities/*.js'],
       entitiesTs: ['./src/types/orm/entities/*.ts'],
-      clientUrl: this.getClientUrl()
-    }
-  }
-
-  private getClientUrl (): string | undefined {
-    switch (this.storageType) {
-      case StorageType.SQLITE:
-      case StorageType.MARIA_DB:
-      case StorageType.MYSQL:
-        return this.storageUri.toString()
     }
   }
 }
index c3c98022d4204c63aa6d940f7cac887d2424ed58..f092351afba5bb21def418cede3ff6f15218b959 100644 (file)
@@ -18,16 +18,15 @@ export class MongoDBStorage extends Storage {
     this.dbName = this.storageUri.pathname.replace(/(?:^\/)|(?:\/$)/g, '')
   }
 
-  public async storePerformanceStatistics (performanceStatistics: Statistics): Promise<void> {
+  public async close (): Promise<void> {
+    this.clearPerformanceStatistics()
     try {
-      this.setPerformanceStatistics(performanceStatistics)
-      this.checkDBConnection()
-      await this.client
-        ?.db(this.dbName)
-        .collection<Statistics>(Constants.PERFORMANCE_RECORDS_TABLE)
-        .replaceOne({ id: performanceStatistics.id }, performanceStatistics, { upsert: true })
+      if (this.connected && this.client != null) {
+        await this.client.close()
+        this.connected = false
+      }
     } catch (error) {
-      this.handleDBError(StorageType.MONGO_DB, error as Error, Constants.PERFORMANCE_RECORDS_TABLE)
+      this.handleDBStorageError(StorageType.MONGO_DB, error as Error)
     }
   }
 
@@ -38,25 +37,33 @@ export class MongoDBStorage extends Storage {
         this.connected = true
       }
     } catch (error) {
-      this.handleDBError(StorageType.MONGO_DB, error as Error)
+      this.handleDBStorageError(StorageType.MONGO_DB, error as Error)
     }
   }
 
-  public async close (): Promise<void> {
-    this.clearPerformanceStatistics()
+  public async storePerformanceStatistics (performanceStatistics: Statistics): Promise<void> {
     try {
-      if (this.connected && this.client != null) {
-        await this.client.close()
-        this.connected = false
-      }
+      this.setPerformanceStatistics(performanceStatistics)
+      this.checkDBConnection()
+      await this.client
+        ?.db(this.dbName)
+        .collection<Statistics>(Constants.PERFORMANCE_RECORDS_TABLE)
+        .replaceOne({ id: performanceStatistics.id }, performanceStatistics, {
+          upsert: true,
+        })
     } catch (error) {
-      this.handleDBError(StorageType.MONGO_DB, error as Error)
+      this.handleDBStorageError(
+        StorageType.MONGO_DB,
+        error as Error,
+        Constants.PERFORMANCE_RECORDS_TABLE
+      )
     }
   }
 
   private checkDBConnection (): void {
     if (this.client == null) {
       throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         `${this.logPrefix} ${this.getDBNameFromStorageType(
           StorageType.MONGO_DB
         )} client initialization failed while trying to issue a request`
@@ -64,6 +71,7 @@ export class MongoDBStorage extends Storage {
     }
     if (!this.connected) {
       throw new BaseError(
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         `${this.logPrefix} ${this.getDBNameFromStorageType(
           StorageType.MONGO_DB
         )} connection not opened while trying to issue a request`
index aae7547f5b32afb656c023106c01babd846205b4..ceda1ab57c8b6c7ad33e90dca5d1b0a9cbabe0c7 100644 (file)
@@ -1,6 +1,7 @@
 // Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
 import type { Statistics } from '../../types/index.js'
+
 import { Storage } from './Storage.js'
 
 export class None extends Storage {
@@ -8,15 +9,15 @@ export class None extends Storage {
     super('none://none', 'none')
   }
 
-  public storePerformanceStatistics (performanceStatistics: Statistics): void {
-    this.setPerformanceStatistics(performanceStatistics)
+  public close (): void {
+    this.clearPerformanceStatistics()
   }
 
   public open (): void {
     /** Intentionally empty */
   }
 
-  public close (): void {
-    this.clearPerformanceStatistics()
+  public storePerformanceStatistics (performanceStatistics: Statistics): void {
+    this.setPerformanceStatistics(performanceStatistics)
   }
 }
index aee3ae9efbb5ca86b17a8a7ddf3fcd01b7b80684..5abe052dd524aa3d9af2ba0534772e887e9513de 100644 (file)
@@ -7,32 +7,72 @@ import {
   type EmptyObject,
   type HandleErrorParams,
   type Statistics,
-  StorageType
+  StorageType,
 } from '../../types/index.js'
-import { logger, setDefaultErrorParams } from '../../utils/index.js'
+import { logger } from '../../utils/index.js'
 
 export abstract class Storage {
-  protected readonly storageUri: URL
-  protected readonly logPrefix: string
-  protected dbName!: string
   private static readonly performanceStatistics = new Map<string, Statistics>()
+  protected dbName!: string
+  protected readonly logPrefix: string
+  protected readonly storageUri: URL
 
   constructor (storageUri: string, logPrefix: string) {
     this.storageUri = new URL(storageUri)
     this.logPrefix = logPrefix
   }
 
-  protected handleDBError (
+  public abstract close (): Promise<void> | void
+
+  public getPerformanceStatistics (): IterableIterator<Statistics> {
+    return Storage.performanceStatistics.values()
+  }
+
+  public abstract open (): Promise<void> | void
+
+  public abstract storePerformanceStatistics (
+    performanceStatistics: Statistics
+  ): Promise<void> | void
+
+  protected clearPerformanceStatistics (): void {
+    Storage.performanceStatistics.clear()
+  }
+
+  protected getDBNameFromStorageType (type: StorageType): DBName | undefined {
+    switch (type) {
+      case StorageType.MARIA_DB:
+        return DBName.MARIA_DB
+      case StorageType.MONGO_DB:
+        return DBName.MONGO_DB
+      case StorageType.MYSQL:
+        return DBName.MYSQL
+      case StorageType.SQLITE:
+        return DBName.SQLITE
+    }
+  }
+
+  protected handleDBStorageError (
     type: StorageType,
     error: Error,
     table?: string,
-    params: HandleErrorParams<EmptyObject> = { throwError: false, consoleOut: false }
+    params: HandleErrorParams<EmptyObject> = {
+      consoleOut: false,
+      throwError: false,
+    }
   ): void {
-    setDefaultErrorParams(params, { throwError: false, consoleOut: false })
+    params = {
+      ...{
+        consoleOut: false,
+        throwError: false,
+      },
+      ...params,
+    }
     const inTableOrCollectionStr = table != null && ` in table or collection '${table}'`
     logger.error(
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       `${this.logPrefix} ${this.getDBNameFromStorageType(type)} error '${
         error.message
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       }'${inTableOrCollectionStr}:`,
       error
     )
@@ -41,34 +81,7 @@ export abstract class Storage {
     }
   }
 
-  protected getDBNameFromStorageType (type: StorageType): DBName | undefined {
-    switch (type) {
-      case StorageType.SQLITE:
-        return DBName.SQLITE
-      case StorageType.MARIA_DB:
-        return DBName.MARIA_DB
-      case StorageType.MYSQL:
-        return DBName.MYSQL
-      case StorageType.MONGO_DB:
-        return DBName.MONGO_DB
-    }
-  }
-
-  public getPerformanceStatistics (): IterableIterator<Statistics> {
-    return Storage.performanceStatistics.values()
-  }
-
   protected setPerformanceStatistics (performanceStatistics: Statistics): void {
     Storage.performanceStatistics.set(performanceStatistics.id, performanceStatistics)
   }
-
-  protected clearPerformanceStatistics (): void {
-    Storage.performanceStatistics.clear()
-  }
-
-  public abstract open (): void | Promise<void>
-  public abstract close (): void | Promise<void>
-  public abstract storePerformanceStatistics (
-    performanceStatistics: Statistics
-  ): void | Promise<void>
 }
index 2b6b84ffe5b8ad75da53fc7c5fa9687b0e0089cd..277e6420420135f98b87729f637b3f29bf127787 100644 (file)
@@ -1,12 +1,13 @@
 // Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
 
+import type { Storage } from './Storage.js'
+
 import { BaseError } from '../../exception/index.js'
 import { StorageType } from '../../types/index.js'
 import { JsonFileStorage } from './JsonFileStorage.js'
 import { MikroOrmStorage } from './MikroOrmStorage.js'
 import { MongoDBStorage } from './MongoDBStorage.js'
 import { None } from './None.js'
-import type { Storage } from './Storage.js'
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class StorageFactory {
@@ -24,14 +25,14 @@ export class StorageFactory {
       case StorageType.JSON_FILE:
         storageInstance = new JsonFileStorage(connectionUri, logPrefix)
         break
-      case StorageType.MONGO_DB:
-        storageInstance = new MongoDBStorage(connectionUri, logPrefix)
-        break
-      case StorageType.SQLITE:
       case StorageType.MARIA_DB:
       case StorageType.MYSQL:
+      case StorageType.SQLITE:
         storageInstance = new MikroOrmStorage(connectionUri, logPrefix, type)
         break
+      case StorageType.MONGO_DB:
+        storageInstance = new MongoDBStorage(connectionUri, logPrefix)
+        break
       case StorageType.NONE:
         storageInstance = new None()
         break
index 19fd71855cdd5ed9451573e72e93deab177c9c46..149d949504678fe26f9c8b336c8866585cb10eaf 100755 (executable)
@@ -1,6 +1,5 @@
-const fs = require('node:fs')
-
 const { MongoClient } = require('mongodb')
+const fs = require('node:fs')
 
 // This script deletes charging stations
 // Filter charging stations by id pattern
@@ -13,8 +12,7 @@ const config = JSON.parse(fs.readFileSync('scriptConfig.json', 'utf8'))
 
 // Mongo Connection and Query
 if (config?.mongoConnectionString) {
-  // eslint-disable-next-line n/handle-callback-err
-  MongoClient.connect(config.mongoConnectionString, async function (_err, client) {
+  MongoClient.connect(config.mongoConnectionString, async (_err, client) => {
     const db = client.db()
 
     for await (const tenantID of config.tenantIDs) {
index d41dc1665bb1c4c7f783b35a1fb05f5e0902d269..8b9e913f39499da7f411582016877fb859fea976 100755 (executable)
@@ -1,6 +1,5 @@
-const fs = require('node:fs')
-
 const { MongoClient } = require('mongodb')
+const fs = require('node:fs')
 
 // This script sets charging stations public or private
 // Filter charging stations by id pattern
@@ -14,8 +13,7 @@ const config = JSON.parse(fs.readFileSync('scriptConfig.json', 'utf8'))
 
 // Mongo Connection and Query
 if (config?.mongoConnectionString) {
-  // eslint-disable-next-line n/handle-callback-err
-  MongoClient.connect(config.mongoConnectionString, async function (_err, client) {
+  MongoClient.connect(config.mongoConnectionString, async (_err, client) => {
     const db = client.db()
 
     for await (const tenantID of config.tenantIDs) {
index 2ff04c09096a42fcc8e33b2a8bb937fa84fd4cc8..61bb9bc3a0c582edd682f4f9ade5f10ecb7af61c 100644 (file)
@@ -1,44 +1,44 @@
 import type { JsonObject } from './JsonType.js'
 
 export enum IdTagDistribution {
+  CONNECTOR_AFFINITY = 'connector-affinity',
   RANDOM = 'random',
   ROUND_ROBIN = 'round-robin',
-  CONNECTOR_AFFINITY = 'connector-affinity'
 }
 
 export interface AutomaticTransactionGeneratorConfiguration extends JsonObject {
   enable: boolean
-  minDuration: number
+  idTagDistribution?: IdTagDistribution
+  maxDelayBetweenTwoTransactions: number
   maxDuration: number
   minDelayBetweenTwoTransactions: number
-  maxDelayBetweenTwoTransactions: number
+  minDuration: number
   probabilityOfStart: number
-  stopAfterHours: number
-  stopAbsoluteDuration: boolean
   requireAuthorize?: boolean
-  idTagDistribution?: IdTagDistribution
+  stopAbsoluteDuration: boolean
+  stopAfterHours: number
+}
+
+export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
+  automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
+  automaticTransactionGeneratorStatuses?: Status[]
 }
 
 export interface Status {
-  start: boolean
-  startDate?: Date
-  lastRunDate?: Date
-  stopDate?: Date
-  stoppedDate?: Date
-  authorizeRequests: number
   acceptedAuthorizeRequests: number
-  rejectedAuthorizeRequests: number
-  startTransactionRequests: number
   acceptedStartTransactionRequests: number
-  rejectedStartTransactionRequests: number
-  stopTransactionRequests: number
   acceptedStopTransactionRequests: number
+  authorizeRequests: number
+  lastRunDate?: Date
+  rejectedAuthorizeRequests: number
+  rejectedStartTransactionRequests: number
   rejectedStopTransactionRequests: number
   skippedConsecutiveTransactions: number
   skippedTransactions: number
-}
-
-export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
-  automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
-  automaticTransactionGeneratorStatuses?: Status[]
+  start: boolean
+  startDate?: Date
+  startTransactionRequests: number
+  stopDate?: Date
+  stoppedDate?: Date
+  stopTransactionRequests: number
 }
index bb42adff99a4c53365b04cb2b25f2473824eef82..bd7bf905fe0c4f28a0e99cfb2359b9d19a117a37 100644 (file)
@@ -4,22 +4,23 @@ import type { ChargingStationOcppConfiguration } from './ChargingStationOcppConf
 import type { ConnectorStatus } from './ConnectorStatus.js'
 import type { EvseStatus } from './Evse.js'
 
-interface ConnectorsConfiguration {
+export type ChargingStationConfiguration =
+  ChargingStationAutomaticTransactionGeneratorConfiguration &
+    ChargingStationInfoConfiguration &
+    ChargingStationOcppConfiguration &
+    ConnectorsConfiguration &
+    EvsesConfiguration & {
+      configurationHash?: string
+    }
+
+export type EvseStatusConfiguration = Omit<EvseStatus, 'connectors'> & {
   connectorsStatus?: ConnectorStatus[]
 }
 
-export type EvseStatusConfiguration = Omit<EvseStatus, 'connectors'> & {
+interface ConnectorsConfiguration {
   connectorsStatus?: ConnectorStatus[]
 }
 
 interface EvsesConfiguration {
   evsesStatus?: EvseStatusConfiguration[]
 }
-
-export type ChargingStationConfiguration = ChargingStationInfoConfiguration &
-ChargingStationOcppConfiguration &
-ChargingStationAutomaticTransactionGeneratorConfiguration &
-ConnectorsConfiguration &
-EvsesConfiguration & {
-  configurationHash?: string
-}
index b690bc916f2dca73d604349dc508f11177ff0643..305b84a837fa4791923f30863d8c2e0f1306e5b8 100644 (file)
@@ -1,12 +1,13 @@
 export enum ChargingStationEvents {
+  accepted = 'accepted',
   added = 'added',
+  connected = 'connected',
+  connectorStatusChanged = 'connectorStatusChanged',
   deleted = 'deleted',
+  disconnected = 'disconnected',
+  pending = 'pending',
+  rejected = 'rejected',
   started = 'started',
   stopped = 'stopped',
   updated = 'updated',
-  registered = 'registered',
-  accepted = 'accepted',
-  rejected = 'rejected',
-  disconnected = 'disconnected',
-  connectorStatusChanged = 'connectorStatusChanged'
 }
index 12327cd891b918cee810e3843f180503f0f41547..09550060a38f9fd344f8a799c616ecb6895ae803 100644 (file)
@@ -2,30 +2,30 @@ import type { ChargingStationTemplate } from './ChargingStationTemplate.js'
 import type { FirmwareStatus } from './ocpp/Requests.js'
 
 export type ChargingStationInfo = Omit<
-ChargingStationTemplate,
-| 'Connectors'
-| 'Evses'
-| 'Configuration'
-| 'AutomaticTransactionGenerator'
-| 'numberOfConnectors'
-| 'power'
-| 'powerUnit'
-| 'chargeBoxSerialNumberPrefix'
-| 'chargePointSerialNumberPrefix'
-| 'meterSerialNumberPrefix'
+  ChargingStationTemplate,
+  | 'AutomaticTransactionGenerator'
+  | 'chargeBoxSerialNumberPrefix'
+  | 'chargePointSerialNumberPrefix'
+  | 'Configuration'
+  | 'Connectors'
+  | 'Evses'
+  | 'meterSerialNumberPrefix'
+  | 'numberOfConnectors'
+  | 'power'
+  | 'powerUnit'
 > & {
+  chargeBoxSerialNumber?: string
+  chargePointSerialNumber?: string
+  chargingStationId?: string
+  firmwareStatus?: FirmwareStatus
   hashId: string
-  templateIndex: number
-  templateName: string
   /** @deprecated Use `hashId` instead. */
   infoHash?: string
-  chargingStationId?: string
-  chargeBoxSerialNumber?: string
-  chargePointSerialNumber?: string
-  meterSerialNumber?: string
-  maximumPower?: number // Always in Watt
   maximumAmperage?: number // Always in Ampere
-  firmwareStatus?: FirmwareStatus
+  maximumPower?: number // Always in Watt
+  meterSerialNumber?: string
+  templateIndex: number
+  templateName: string
 }
 
 export interface ChargingStationInfoConfiguration {
index fdf03a40574765aa9fd7247f13edb18305c23554..cdd1d3ff17ca7ee3bb655cf9f0eed41bffd71fae 100644 (file)
@@ -1,11 +1,11 @@
 import type { JsonObject } from './JsonType.js'
 import type { OCPPConfigurationKey } from './ocpp/Configuration.js'
 
-export interface ConfigurationKey extends OCPPConfigurationKey {
-  visible?: boolean
-  reboot?: boolean
-}
-
 export interface ChargingStationOcppConfiguration extends JsonObject {
   configurationKey?: ConfigurationKey[]
 }
+
+export interface ConfigurationKey extends OCPPConfigurationKey {
+  reboot?: boolean
+  visible?: boolean
+}
index b3cc773489d0f384feb8bf93cb5934039628eeae..c17544f736c4b8e0d1d60254590e6bb1aee731a5 100644 (file)
@@ -1,5 +1,4 @@
 import type { ClientRequestArgs } from 'node:http'
-
 import type { ClientOptions } from 'ws'
 
 import type { AutomaticTransactionGeneratorConfiguration } from './AutomaticTransactionGenerator.js'
@@ -13,123 +12,123 @@ import type {
   FirmwareStatus,
   IncomingRequestCommand,
   MessageTrigger,
-  RequestCommand
+  RequestCommand,
 } from './ocpp/Requests.js'
 
+export enum AmpereUnits {
+  AMPERE = 'A',
+  CENTI_AMPERE = 'cA',
+  DECI_AMPERE = 'dA',
+  MILLI_AMPERE = 'mA',
+}
+
 export enum CurrentType {
   AC = 'AC',
-  DC = 'DC'
+  DC = 'DC',
 }
 
 export enum PowerUnits {
+  KILO_WATT = 'kW',
   WATT = 'W',
-  KILO_WATT = 'kW'
-}
-
-export enum AmpereUnits {
-  MILLI_AMPERE = 'mA',
-  CENTI_AMPERE = 'cA',
-  DECI_AMPERE = 'dA',
-  AMPERE = 'A'
 }
 
 export enum Voltage {
   VOLTAGE_110 = 110,
   VOLTAGE_230 = 230,
   VOLTAGE_400 = 400,
-  VOLTAGE_800 = 800
-}
-
-export type WsOptions = ClientOptions & ClientRequestArgs
-
-export interface FirmwareUpgrade extends JsonObject {
-  versionUpgrade?: {
-    patternGroup?: number
-    step?: number
-  }
-  reset?: boolean
-  failureStatus?: FirmwareStatus
-}
-
-interface CommandsSupport extends JsonObject {
-  incomingCommands: Record<IncomingRequestCommand, boolean>
-  outgoingCommands?: Record<RequestCommand, boolean>
+  VOLTAGE_800 = 800,
 }
 
 enum x509CertificateType {
-  V2GRootCertificate = 'V2GRootCertificate',
-  MORootCertificate = 'MORootCertificate',
+  ChargingStationCertificate = 'ChargingStationCertificate',
   CSMSRootCertificate = 'CSMSRootCertificate',
   ManufacturerRootCertificate = 'ManufacturerRootCertificate',
-  ChargingStationCertificate = 'ChargingStationCertificate',
-  V2GCertificate = 'V2GCertificate'
+  MORootCertificate = 'MORootCertificate',
+  V2GCertificate = 'V2GCertificate',
+  V2GRootCertificate = 'V2GRootCertificate',
 }
 
 export interface ChargingStationTemplate {
-  templateHash?: string
-  supervisionUrls?: string | string[]
-  supervisionUrlOcppConfiguration?: boolean
-  supervisionUrlOcppKey?: string
-  supervisionUser?: string
-  supervisionPassword?: string
-  autoStart?: boolean
-  ocppVersion?: OCPPVersion
-  ocppProtocol?: OCPPProtocol
-  ocppStrictCompliance?: boolean
-  ocppPersistentConfiguration?: boolean
-  stationInfoPersistentConfiguration?: boolean
+  amperageLimitationOcppKey?: string
+  amperageLimitationUnit?: AmpereUnits
+  AutomaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
   automaticTransactionGeneratorPersistentConfiguration?: boolean
-  wsOptions?: WsOptions
-  idTagsFile?: string
+  autoReconnectMaxRetries?: number
+  autoRegister?: boolean
+  autoStart?: boolean
   baseName: string
-  nameSuffix?: string
-  fixedName?: boolean
+  beginEndMeterValues?: boolean
+  chargeBoxSerialNumberPrefix?: string
   chargePointModel: string
-  chargePointVendor: string
   chargePointSerialNumberPrefix?: string
-  chargeBoxSerialNumberPrefix?: string
-  firmwareVersionPattern?: string
-  firmwareVersion?: string
+  chargePointVendor: string
+  commandsSupport?: CommandsSupport
+  Configuration?: ChargingStationOcppConfiguration
+  Connectors?: Record<string, ConnectorStatus>
+  currentOutType?: CurrentType
+  customValueLimitationMeterValues?: boolean
+  enableStatistics?: boolean
+  Evses?: Record<string, EvseTemplate>
   firmwareUpgrade?: FirmwareUpgrade
+  firmwareVersion?: string
+  firmwareVersionPattern?: string
+  fixedName?: boolean
   iccid?: string
+  idTagsFile?: string
   imsi?: string
+  mainVoltageMeterValues?: boolean
+  messageTriggerSupport?: Record<MessageTrigger, boolean>
+  meteringPerTransaction?: boolean
   meterSerialNumberPrefix?: string
   meterType?: string
+  /** @deprecated Replaced by remoteAuthorization. */
+  mustAuthorizeAtRemoteStart?: boolean
+  nameSuffix?: string
+  numberOfConnectors?: number | number[]
+  numberOfPhases?: number
+  ocppPersistentConfiguration?: boolean
+  ocppProtocol?: OCPPProtocol
+  ocppStrictCompliance?: boolean
+  ocppVersion?: OCPPVersion
+  outOfOrderEndMeterValues?: boolean
+  /** @deprecated Replaced by ocppStrictCompliance. */
+  payloadSchemaValidation?: boolean
+  phaseLineToLineVoltageMeterValues?: boolean
   power?: number | number[]
-  powerUnit?: PowerUnits
   powerSharedByConnectors?: boolean
-  currentOutType?: CurrentType
-  voltageOut?: Voltage
-  numberOfPhases?: number
-  numberOfConnectors?: number | number[]
-  useConnectorId0?: boolean
+  powerUnit?: PowerUnits
   randomConnectors?: boolean
-  resetTime?: number
-  autoRegister?: boolean
-  autoReconnectMaxRetries?: number
   reconnectExponentialDelay?: boolean
   registrationMaxRetries?: number
-  enableStatistics?: boolean
   remoteAuthorization?: boolean
-  /** @deprecated Replaced by remoteAuthorization. */
-  mustAuthorizeAtRemoteStart?: boolean
-  /** @deprecated Replaced by ocppStrictCompliance. */
-  payloadSchemaValidation?: boolean
-  amperageLimitationOcppKey?: string
-  amperageLimitationUnit?: AmpereUnits
-  beginEndMeterValues?: boolean
-  outOfOrderEndMeterValues?: boolean
-  meteringPerTransaction?: boolean
-  transactionDataMeterValues?: boolean
+  resetTime?: number
+  stationInfoPersistentConfiguration?: boolean
   stopTransactionsOnStopped?: boolean
-  mainVoltageMeterValues?: boolean
-  phaseLineToLineVoltageMeterValues?: boolean
-  customValueLimitationMeterValues?: boolean
-  commandsSupport?: CommandsSupport
-  messageTriggerSupport?: Record<MessageTrigger, boolean>
-  Configuration?: ChargingStationOcppConfiguration
-  AutomaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
-  Evses?: Record<string, EvseTemplate>
-  Connectors?: Record<string, ConnectorStatus>
+  supervisionPassword?: string
+  supervisionUrlOcppConfiguration?: boolean
+  supervisionUrlOcppKey?: string
+  supervisionUrls?: string | string[]
+  supervisionUser?: string
+  templateHash?: string
+  transactionDataMeterValues?: boolean
+  useConnectorId0?: boolean
+  voltageOut?: Voltage
+  wsOptions?: WsOptions
   x509Certificates?: Record<x509CertificateType, string>
 }
+
+export interface FirmwareUpgrade extends JsonObject {
+  failureStatus?: FirmwareStatus
+  reset?: boolean
+  versionUpgrade?: {
+    patternGroup?: number
+    step?: number
+  }
+}
+
+export type WsOptions = ClientOptions & ClientRequestArgs
+
+interface CommandsSupport extends JsonObject {
+  incomingCommands: Record<IncomingRequestCommand, boolean>
+  outgoingCommands?: Record<RequestCommand, boolean>
+}
index 4d47a5e3698bb96a5bad1e9f3093af396993be32..05e3a85f86b9c9ad04637cfd446e283c83f900e4 100644 (file)
@@ -2,7 +2,6 @@ import type { WebSocket } from 'ws'
 
 import type { WorkerData } from '../worker/index.js'
 import type { ChargingStationAutomaticTransactionGeneratorConfiguration } from './AutomaticTransactionGenerator.js'
-import { ChargingStationEvents } from './ChargingStationEvents.js'
 import type { ChargingStationInfo } from './ChargingStationInfo.js'
 import type { ChargingStationOcppConfiguration } from './ChargingStationOcppConfiguration.js'
 import type { ConnectorStatus } from './ConnectorStatus.js'
@@ -11,58 +10,60 @@ import type { JsonObject } from './JsonType.js'
 import type { BootNotificationResponse } from './ocpp/Responses.js'
 import type { Statistics } from './Statistics.js'
 
+import { ChargingStationEvents } from './ChargingStationEvents.js'
+
+enum ChargingStationMessageEvents {
+  performanceStatistics = 'performanceStatistics',
+}
+
+export interface ChargingStationData extends WorkerData {
+  automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
+  bootNotificationResponse?: BootNotificationResponse
+  connectors: ConnectorStatus[]
+  evses: EvseStatusWorkerType[]
+  ocppConfiguration: ChargingStationOcppConfiguration
+  started: boolean
+  stationInfo: ChargingStationInfo
+  supervisionUrl: string
+  wsState?:
+    | typeof WebSocket.CLOSED
+    | typeof WebSocket.CLOSING
+    | typeof WebSocket.CONNECTING
+    | typeof WebSocket.OPEN
+}
+
 export interface ChargingStationOptions extends JsonObject {
-  supervisionUrls?: string | string[]
-  persistentConfiguration?: boolean
-  autoStart?: boolean
   autoRegister?: boolean
+  autoStart?: boolean
   enableStatistics?: boolean
   ocppStrictCompliance?: boolean
+  persistentConfiguration?: boolean
   stopTransactionsOnStopped?: boolean
+  supervisionUrls?: string | string[]
 }
 
 export interface ChargingStationWorkerData extends WorkerData {
   index: number
-  templateFile: string
   options?: ChargingStationOptions
+  templateFile: string
 }
 
-export type EvseStatusWorkerType = Omit<EvseStatus, 'connectors'> & {
-  connectors?: ConnectorStatus[]
-}
-
-export interface ChargingStationData extends WorkerData {
-  started: boolean
-  stationInfo: ChargingStationInfo
-  connectors: ConnectorStatus[]
-  evses: EvseStatusWorkerType[]
-  ocppConfiguration: ChargingStationOcppConfiguration
-  supervisionUrl: string
-  wsState?:
-  | typeof WebSocket.CONNECTING
-  | typeof WebSocket.OPEN
-  | typeof WebSocket.CLOSING
-  | typeof WebSocket.CLOSED
-  bootNotificationResponse?: BootNotificationResponse
-  automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
+export interface ChargingStationWorkerMessage<T extends ChargingStationWorkerMessageData> {
+  data: T
+  event: ChargingStationWorkerMessageEvents
 }
 
-enum ChargingStationMessageEvents {
-  performanceStatistics = 'performanceStatistics'
-}
+export type ChargingStationWorkerMessageData = ChargingStationData | Statistics
 
 export const ChargingStationWorkerMessageEvents = {
   ...ChargingStationEvents,
-  ...ChargingStationMessageEvents
+  ...ChargingStationMessageEvents,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ChargingStationWorkerMessageEvents =
   | ChargingStationEvents
   | ChargingStationMessageEvents
 
-export type ChargingStationWorkerMessageData = ChargingStationData | Statistics
-
-export interface ChargingStationWorkerMessage<T extends ChargingStationWorkerMessageData> {
-  event: ChargingStationWorkerMessageEvents
-  data: T
+export type EvseStatusWorkerType = Omit<EvseStatus, 'connectors'> & {
+  connectors?: ConnectorStatus[]
 }
index ffbb7deca5994abb61e9b5fc4736e4fb14b1cf61..d66050241c8f67f538fbea99c0ab74daf9b623f0 100644 (file)
 import type { ListenOptions } from 'node:net'
 import type { ResourceLimits } from 'node:worker_threads'
-
 import type { WorkerChoiceStrategy } from 'poolifier'
 
 import type { WorkerProcessType } from '../worker/index.js'
 import type { StorageType } from './Storage.js'
 import type { ApplicationProtocol, AuthenticationType } from './UIProtocol.js'
 
-type ServerOptions = ListenOptions
+export enum ApplicationProtocolVersion {
+  VERSION_11 = '1.1',
+  VERSION_20 = '2.0',
+}
 
 export enum ConfigurationSection {
   log = 'log',
   performanceStorage = 'performanceStorage',
+  uiServer = 'uiServer',
   worker = 'worker',
-  uiServer = 'uiServer'
 }
 
 export enum SupervisionUrlDistribution {
-  ROUND_ROBIN = 'round-robin',
+  CHARGING_STATION_AFFINITY = 'charging-station-affinity',
   RANDOM = 'random',
-  CHARGING_STATION_AFFINITY = 'charging-station-affinity'
+  ROUND_ROBIN = 'round-robin',
 }
 
-export interface StationTemplateUrl {
-  file: string
-  numberOfStations: number
-  provisionedNumberOfStations?: number
+export interface ConfigurationData {
+  /** @deprecated Moved to charging station template. */
+  autoReconnectMaxRetries?: number
+  /** @deprecated Moved to worker configuration section. */
+  chargingStationsPerWorker?: number
+  /** @deprecated Moved to worker configuration section. */
+  elementAddDelay?: number
+  log?: LogConfiguration
+  /** @deprecated Moved to log configuration section. */
+  logConsole?: boolean
+  /** @deprecated Moved to log configuration section. */
+  logEnabled?: boolean
+  /** @deprecated Moved to log configuration section. */
+  logErrorFile?: string
+  /** @deprecated Moved to log configuration section. */
+  logFile?: string
+  /** @deprecated Moved to log configuration section. */
+  logFormat?: string
+  /** @deprecated Moved to log configuration section. */
+  logLevel?: string
+  /** @deprecated Moved to log configuration section. */
+  logMaxFiles?: number | string
+  /** @deprecated Moved to log configuration section. */
+  logMaxSize?: number | string
+  /** @deprecated Moved to log configuration section. */
+  logRotate?: boolean
+  /** @deprecated Moved to log configuration section. */
+  logStatisticsInterval?: number
+  performanceStorage?: StorageConfiguration
+  stationTemplateUrls: StationTemplateUrl[]
+  supervisionUrlDistribution?: SupervisionUrlDistribution
+  supervisionUrls?: string | string[]
+  uiServer?: UIServerConfiguration
+  worker?: WorkerConfiguration
+  /** @deprecated Moved to worker configuration section. */
+  workerPoolMaxSize?: number
+  /** @deprecated Moved to worker configuration section. */
+  workerPoolMinSize?: number
+  /** @deprecated Moved to worker configuration section. */
+  workerPoolStrategy?: WorkerChoiceStrategy
+  /** @deprecated Moved to worker configuration section. */
+  workerProcess?: WorkerProcessType
+  /** @deprecated Moved to worker configuration section. */
+  workerStartDelay?: number
 }
 
+export type ElementsPerWorkerType = 'all' | 'auto' | number
+
 export interface LogConfiguration {
+  console?: boolean
   enabled?: boolean
-  file?: string
   errorFile?: string
-  statisticsInterval?: number
-  level?: string
-  console?: boolean
+  file?: string
   format?: string
+  level?: string
+  maxFiles?: number | string
+  maxSize?: number | string
   rotate?: boolean
-  maxFiles?: string | number
-  maxSize?: string | number
+  statisticsInterval?: number
 }
 
-export enum ApplicationProtocolVersion {
-  VERSION_11 = 1.1,
-  VERSION_20 = 2.0
+export interface StationTemplateUrl {
+  file: string
+  numberOfStations: number
+  provisionedNumberOfStations?: number
 }
 
-export interface UIServerConfiguration {
+export interface StorageConfiguration {
   enabled?: boolean
-  type?: ApplicationProtocol
-  version?: ApplicationProtocolVersion
-  options?: ServerOptions
+  type?: StorageType
+  uri?: string
+}
+
+export interface UIServerConfiguration {
   authentication?: {
     enabled: boolean
+    password?: string
     type: AuthenticationType
     username?: string
-    password?: string
   }
-}
-
-export interface StorageConfiguration {
   enabled?: boolean
-  type?: StorageType
-  uri?: string
+  options?: ServerOptions
+  type?: ApplicationProtocol
+  version?: ApplicationProtocolVersion
 }
 
-export type ElementsPerWorkerType = number | 'auto' | 'all'
-
 export interface WorkerConfiguration {
-  processType?: WorkerProcessType
-  startDelay?: number
+  elementAddDelay?: number
   elementsPerWorker?: ElementsPerWorkerType
   /** @deprecated Use `elementAddDelay` instead. */
   elementStartDelay?: number
-  elementAddDelay?: number
-  poolMinSize?: number
   poolMaxSize?: number
+  poolMinSize?: number
+  processType?: WorkerProcessType
   resourceLimits?: ResourceLimits
+  startDelay?: number
 }
 
-export interface ConfigurationData {
-  supervisionUrls?: string | string[]
-  supervisionUrlDistribution?: SupervisionUrlDistribution
-  stationTemplateUrls: StationTemplateUrl[]
-  log?: LogConfiguration
-  worker?: WorkerConfiguration
-  uiServer?: UIServerConfiguration
-  performanceStorage?: StorageConfiguration
-  /** @deprecated Moved to charging station template. */
-  autoReconnectMaxRetries?: number
-  /** @deprecated Moved to worker configuration section. */
-  workerProcess?: WorkerProcessType
-  /** @deprecated Moved to worker configuration section. */
-  workerStartDelay?: number
-  /** @deprecated Moved to worker configuration section. */
-  elementAddDelay?: number
-  /** @deprecated Moved to worker configuration section. */
-  workerPoolMinSize?: number
-  /** @deprecated Moved to worker configuration section. */
-  workerPoolMaxSize?: number
-  /** @deprecated Moved to worker configuration section. */
-  workerPoolStrategy?: WorkerChoiceStrategy
-  /** @deprecated Moved to worker configuration section. */
-  chargingStationsPerWorker?: number
-  /** @deprecated Moved to log configuration section. */
-  logStatisticsInterval?: number
-  /** @deprecated Moved to log configuration section. */
-  logEnabled?: boolean
-  /** @deprecated Moved to log configuration section. */
-  logConsole?: boolean
-  /** @deprecated Moved to log configuration section. */
-  logFormat?: string
-  /** @deprecated Moved to log configuration section. */
-  logLevel?: string
-  /** @deprecated Moved to log configuration section. */
-  logRotate?: boolean
-  /** @deprecated Moved to log configuration section. */
-  logMaxFiles?: number | string
-  /** @deprecated Moved to log configuration section. */
-  logMaxSize?: number | string
-  /** @deprecated Moved to log configuration section. */
-  logFile?: string
-  /** @deprecated Moved to log configuration section. */
-  logErrorFile?: string
-}
+type ServerOptions = ListenOptions
index 26f5433424fa5404000760720d3a7a06ea8d8022..b9fef35e0026e540c3501e7f147018490d1bf8d9 100644 (file)
@@ -6,23 +6,23 @@ import type { AvailabilityType } from './ocpp/Requests.js'
 import type { Reservation } from './ocpp/Reservation.js'
 
 export interface ConnectorStatus {
+  authorizeIdTag?: string
   availability: AvailabilityType
   bootStatus?: ConnectorStatusEnum
-  status?: ConnectorStatusEnum
-  MeterValues: SampledValueTemplate[]
-  authorizeIdTag?: string
+  chargingProfiles?: ChargingProfile[]
+  energyActiveImportRegisterValue?: number // In Wh
   idTagAuthorized?: boolean
-  localAuthorizeIdTag?: string
   idTagLocalAuthorized?: boolean
-  transactionRemoteStarted?: boolean
-  transactionStarted?: boolean
-  transactionStart?: Date
+  localAuthorizeIdTag?: string
+  MeterValues: SampledValueTemplate[]
+  reservation?: Reservation
+  status?: ConnectorStatusEnum
+  transactionBeginMeterValue?: MeterValue
+  transactionEnergyActiveImportRegisterValue?: number // In Wh
   transactionId?: number
-  transactionSetInterval?: NodeJS.Timeout
   transactionIdTag?: string
-  energyActiveImportRegisterValue?: number // In Wh
-  transactionEnergyActiveImportRegisterValue?: number // In Wh
-  transactionBeginMeterValue?: MeterValue
-  chargingProfiles?: ChargingProfile[]
-  reservation?: Reservation
+  transactionRemoteStarted?: boolean
+  transactionSetInterval?: NodeJS.Timeout
+  transactionStart?: Date
+  transactionStarted?: boolean
 }
index 34bad1ad4eabbf612fa2c27398def5fea4434f73..89d20479aa550d4abddf6c7eedc2ff1b512aece4 100644 (file)
@@ -1,7 +1,7 @@
 import type { JsonType } from './JsonType.js'
 
 export interface HandleErrorParams<T extends JsonType> {
-  throwError?: boolean
   consoleOut?: boolean
   errorResponse?: T
+  throwError?: boolean
 }
index 8fb09e48d1ef8d1dc2af12412f21656dcdb7fc8c..96eba54d065acf74cdcc8b78a53ffa66efe7e03f 100644 (file)
@@ -1,11 +1,11 @@
 import type { ConnectorStatus } from './ConnectorStatus.js'
 import type { AvailabilityType } from './ocpp/Requests.js'
 
-export interface EvseTemplate {
-  Connectors: Record<string, ConnectorStatus>
-}
-
 export interface EvseStatus {
-  connectors: Map<number, ConnectorStatus>
   availability: AvailabilityType
+  connectors: Map<number, ConnectorStatus>
+}
+
+export interface EvseTemplate {
+  Connectors: Record<string, ConnectorStatus>
 }
index 2b6bc380cbe401c1c294407c489ed47f9bc331fc..9071002b78acdc408cbfa00a3f2391973538d3bb 100644 (file)
@@ -1,8 +1,8 @@
 export enum FileType {
   Authorization = 'authorization',
-  Configuration = 'configuration',
   ChargingStationConfiguration = 'charging station configuration',
   ChargingStationTemplate = 'charging station template',
+  Configuration = 'configuration',
+  JsonSchema = 'json schema',
   PerformanceRecords = 'performance records',
-  JsonSchema = 'json schema'
 }
index 17db232c50cb7c55ec21d80cb0aacd1137010415..515ce25fcd854497cc0e55e25ff5b9681f80f8e8 100644 (file)
@@ -1,7 +1,7 @@
-type JsonPrimitive = string | number | boolean | Date | null
-
 export type JsonObject = {
   [key in string]?: JsonType
 }
 
-export type JsonType = JsonPrimitive | JsonType[] | JsonObject
+export type JsonType = JsonObject | JsonPrimitive | JsonType[]
+
+type JsonPrimitive = boolean | Date | null | number | string
index 8543ef27d7789b8bf1151ac5911c6acc25d4e5dd..cde4c7bcc3bd82a92add72c9636a57e230bdbe29 100644 (file)
@@ -1,4 +1,4 @@
 export enum MapStringifyFormat {
   array = 'array',
-  object = 'object'
+  object = 'object',
 }
index 99572673edbfaa7d36450b860b8f190d13522d4c..f35f4f64fe9079df592f1fb3ccdc9265d52c0f06 100644 (file)
@@ -1,12 +1,12 @@
 import type { SampledValue } from './ocpp/MeterValues.js'
 
-export interface SampledValueTemplate extends SampledValue {
-  fluctuationPercent?: number
-  minimumValue?: number
-}
-
 export interface MeasurandPerPhaseSampledValueTemplates {
   L1?: SampledValueTemplate
   L2?: SampledValueTemplate
   L3?: SampledValueTemplate
 }
+
+export interface SampledValueTemplate extends SampledValue {
+  fluctuationPercent?: number
+  minimumValue?: number
+}
index 833efa8237537b29f83189b527d58df3fd0478f1..1c13b4fb4e8d6cfe24e6fbd1748a8857237ae8b5 100644 (file)
@@ -1,6 +1,6 @@
 export interface MeasurandValues {
+  allPhases: number
   L1: number
   L2: number
   L3: number
-  allPhases: number
 }
index 0ba5307897af6e8939bdc05f92ea186df0bf5172..a567316b8d60d2c403828f030307449df97da633 100644 (file)
@@ -2,8 +2,8 @@ import type { ConfigurationData } from './ConfigurationData.js'
 import type { TemplateStatistics } from './Statistics.js'
 
 export interface SimulatorState {
-  version: string
   configuration: ConfigurationData | undefined
   started: boolean
   templateStatistics: Map<string, TemplateStatistics>
+  version: string
 }
index 89511b0291ba22054c70b6f6583960f4ec4b8129..02927e0725dbd4a9ff4432059d2169df063d58a6 100644 (file)
@@ -3,40 +3,40 @@ import type { CircularBuffer } from 'mnemonist'
 import type { WorkerData } from '../worker/index.js'
 import type { IncomingRequestCommand, RequestCommand } from './ocpp/Requests.js'
 
-export interface TimestampedData {
-  timestamp: number
-  value: number
+export interface Statistics extends WorkerData {
+  createdAt: Date
+  id: string
+  name: string
+  statisticsData: Map<IncomingRequestCommand | RequestCommand | string, StatisticsData>
+  updatedAt?: Date
+  uri: string
 }
 
 export type StatisticsData = Partial<{
-  requestCount: number
-  responseCount: number
-  errorCount: number
-  timeMeasurementCount: number
-  measurementTimeSeries: CircularBuffer<TimestampedData>
+  avgTimeMeasurement: number
   currentTimeMeasurement: number
-  minTimeMeasurement: number
+  errorCount: number
   maxTimeMeasurement: number
-  totalTimeMeasurement: number
-  avgTimeMeasurement: number
+  measurementTimeSeries: CircularBuffer<TimestampedData> | TimestampedData[]
   medTimeMeasurement: number
+  minTimeMeasurement: number
   ninetyFiveThPercentileTimeMeasurement: number
-  stdDevTimeMeasurement: number
+  requestCount: number
+  responseCount: number
+  stdTimeMeasurement: number
+  timeMeasurementCount: number
+  totalTimeMeasurement: number
 }>
 
-export interface Statistics extends WorkerData {
-  id: string
-  name: string
-  uri: string
-  createdAt: Date
-  updatedAt?: Date
-  statisticsData: Map<string | RequestCommand | IncomingRequestCommand, StatisticsData>
-}
-
 export interface TemplateStatistics {
+  added: number
   configured: number
+  indexes: Set<number>
   provisioned: number
-  added: number
   started: number
-  indexes: Set<number>
+}
+
+export interface TimestampedData {
+  timestamp: number
+  value: number
 }
index 232c9d41069d86b61a3f9ff661c20a1fc812f389..f2d73330b4ecae8f4521690df871bc1289df7db1 100644 (file)
@@ -1,15 +1,15 @@
+export enum DBName {
+  MARIA_DB = 'MariaDB',
+  MONGO_DB = 'MongoDB',
+  MYSQL = 'MySQL',
+  SQLITE = 'SQLite',
+}
+
 export enum StorageType {
-  NONE = 'none',
   JSON_FILE = 'jsonfile',
+  MARIA_DB = 'mariadb',
   MONGO_DB = 'mongodb',
   MYSQL = 'mysql',
-  MARIA_DB = 'mariadb',
-  SQLITE = 'sqlite'
-}
-
-export enum DBName {
-  MONGO_DB = 'MongoDB',
-  MYSQL = 'MySQL',
-  MARIA_DB = 'MariaDB',
-  SQLITE = 'SQLite'
+  NONE = 'none',
+  SQLITE = 'sqlite',
 }
index b5cf30fff02cbca47089e577d75a0a8a8e7f0bd2..76e3ee7e3a78441378da4ceccfe6d356a4d2b7f1 100644 (file)
@@ -1,22 +1,54 @@
 import type { JsonObject } from './JsonType.js'
 import type { BroadcastChannelResponsePayload } from './WorkerBroadcastChannel.js'
 
-export enum Protocol {
-  UI = 'ui'
-}
-
 export enum ApplicationProtocol {
   HTTP = 'http',
-  WS = 'ws'
+  WS = 'ws',
 }
 
 export enum AuthenticationType {
   BASIC_AUTH = 'basic-auth',
-  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth'
+  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth',
+}
+
+export enum ProcedureName {
+  ADD_CHARGING_STATIONS = 'addChargingStations',
+  AUTHORIZE = 'authorize',
+  BOOT_NOTIFICATION = 'bootNotification',
+  CLOSE_CONNECTION = 'closeConnection',
+  DATA_TRANSFER = 'dataTransfer',
+  DELETE_CHARGING_STATIONS = 'deleteChargingStations',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
+  FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification',
+  HEARTBEAT = 'heartbeat',
+  LIST_CHARGING_STATIONS = 'listChargingStations',
+  LIST_TEMPLATES = 'listTemplates',
+  METER_VALUES = 'meterValues',
+  OPEN_CONNECTION = 'openConnection',
+  PERFORMANCE_STATISTICS = 'performanceStatistics',
+  SET_SUPERVISION_URL = 'setSupervisionUrl',
+  SIMULATOR_STATE = 'simulatorState',
+  START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator',
+  START_CHARGING_STATION = 'startChargingStation',
+  START_SIMULATOR = 'startSimulator',
+  START_TRANSACTION = 'startTransaction',
+  STATUS_NOTIFICATION = 'statusNotification',
+  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
+  STOP_CHARGING_STATION = 'stopChargingStation',
+  STOP_SIMULATOR = 'stopSimulator',
+  STOP_TRANSACTION = 'stopTransaction',
+}
+
+export enum Protocol {
+  UI = 'ui',
 }
 
 export enum ProtocolVersion {
-  '0.0.1' = '0.0.1'
+  '0.0.1' = '0.0.1',
+}
+export enum ResponseStatus {
+  FAILURE = 'failure',
+  SUCCESS = 'success',
 }
 
 export type ProtocolRequest = [
@@ -24,58 +56,26 @@ export type ProtocolRequest = [
   ProcedureName,
   RequestPayload
 ]
-export type ProtocolResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ResponsePayload
-]
 
 export type ProtocolRequestHandler = (
   uuid?: `${string}-${string}-${string}-${string}-${string}`,
   procedureName?: ProcedureName,
   payload?: RequestPayload
-) => undefined | Promise<undefined> | ResponsePayload | Promise<ResponsePayload>
+) => Promise<ResponsePayload> | Promise<undefined> | ResponsePayload | undefined
 
-export enum ProcedureName {
-  SIMULATOR_STATE = 'simulatorState',
-  START_SIMULATOR = 'startSimulator',
-  STOP_SIMULATOR = 'stopSimulator',
-  LIST_TEMPLATES = 'listTemplates',
-  LIST_CHARGING_STATIONS = 'listChargingStations',
-  ADD_CHARGING_STATIONS = 'addChargingStations',
-  DELETE_CHARGING_STATIONS = 'deleteChargingStations',
-  PERFORMANCE_STATISTICS = 'performanceStatistics',
-  START_CHARGING_STATION = 'startChargingStation',
-  STOP_CHARGING_STATION = 'stopChargingStation',
-  OPEN_CONNECTION = 'openConnection',
-  CLOSE_CONNECTION = 'closeConnection',
-  START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator',
-  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
-  SET_SUPERVISION_URL = 'setSupervisionUrl',
-  START_TRANSACTION = 'startTransaction',
-  STOP_TRANSACTION = 'stopTransaction',
-  AUTHORIZE = 'authorize',
-  BOOT_NOTIFICATION = 'bootNotification',
-  STATUS_NOTIFICATION = 'statusNotification',
-  HEARTBEAT = 'heartbeat',
-  METER_VALUES = 'meterValues',
-  DATA_TRANSFER = 'dataTransfer',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
-  FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification'
-}
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
 
 export interface RequestPayload extends JsonObject {
-  hashIds?: string[]
   connectorIds?: number[]
-}
-
-export enum ResponseStatus {
-  SUCCESS = 'success',
-  FAILURE = 'failure'
+  hashIds?: string[]
 }
 
 export interface ResponsePayload extends JsonObject {
-  status: ResponseStatus
-  hashIdsSucceeded?: string[]
   hashIdsFailed?: string[]
+  hashIdsSucceeded?: string[]
   responsesFailed?: BroadcastChannelResponsePayload[]
+  status: ResponseStatus
 }
index 2f7269aaf2ea563271745b9826b9f877382d0d5a..65fffc5771d618fadb2be657394b48091a9afadb 100644 (file)
@@ -15,9 +15,10 @@ export const WebSocketCloseEventStatusString: Record<WebSocketCloseEventStatusCo
     1012: 'Service Restart',
     1013: 'Try Again Later',
     1014: 'Bad Gateway',
-    1015: 'TLS Handshake'
+    1015: 'TLS Handshake',
   })
 
+/* eslint-disable perfectionist/sort-enums */
 export enum WebSocketCloseEventStatusCode {
   CLOSE_NORMAL = 1000,
   CLOSE_GOING_AWAY = 1001,
@@ -34,8 +35,9 @@ export enum WebSocketCloseEventStatusCode {
   CLOSE_SERVICE_RESTART = 1012,
   CLOSE_TRY_AGAIN_LATER = 1013,
   CLOSE_BAD_GATEWAY = 1014,
-  CLOSE_TLS_HANDSHAKE = 1015
+  CLOSE_TLS_HANDSHAKE = 1015,
 }
+/* eslint-enable perfectionist/sort-enums */
 
 export interface WSError extends Error {
   code?: string
index 077c2f3750acce4fe2e6ece971e8aff3ba5d5d09..97811d934f1191be6d3e72fd08ad6070dbbf8020 100644 (file)
@@ -1,43 +1,44 @@
 import type { RequestPayload, ResponsePayload } from './UIProtocol.js'
 
-export type BroadcastChannelRequest = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  BroadcastChannelProcedureName,
-  BroadcastChannelRequestPayload
-]
-export type BroadcastChannelResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  BroadcastChannelResponsePayload
-]
-
 export enum BroadcastChannelProcedureName {
-  START_CHARGING_STATION = 'startChargingStation',
-  STOP_CHARGING_STATION = 'stopChargingStation',
+  AUTHORIZE = 'authorize',
+  BOOT_NOTIFICATION = 'bootNotification',
+  CLOSE_CONNECTION = 'closeConnection',
+  DATA_TRANSFER = 'dataTransfer',
   DELETE_CHARGING_STATIONS = 'deleteChargingStations',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
+  FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification',
+  HEARTBEAT = 'heartbeat',
+  METER_VALUES = 'meterValues',
   OPEN_CONNECTION = 'openConnection',
-  CLOSE_CONNECTION = 'closeConnection',
-  START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator',
-  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
   SET_SUPERVISION_URL = 'setSupervisionUrl',
+  START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator',
+  START_CHARGING_STATION = 'startChargingStation',
   START_TRANSACTION = 'startTransaction',
-  STOP_TRANSACTION = 'stopTransaction',
-  AUTHORIZE = 'authorize',
-  BOOT_NOTIFICATION = 'bootNotification',
   STATUS_NOTIFICATION = 'statusNotification',
-  HEARTBEAT = 'heartbeat',
-  METER_VALUES = 'meterValues',
-  DATA_TRANSFER = 'dataTransfer',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
-  FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification'
+  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
+  STOP_CHARGING_STATION = 'stopChargingStation',
+  STOP_TRANSACTION = 'stopTransaction',
 }
 
+export type BroadcastChannelRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  BroadcastChannelProcedureName,
+  BroadcastChannelRequestPayload
+]
+
 export interface BroadcastChannelRequestPayload extends RequestPayload {
   connectorId?: number
   transactionId?: number
 }
 
+export type BroadcastChannelResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  BroadcastChannelResponsePayload
+]
+
 export interface BroadcastChannelResponsePayload
-  extends Omit<ResponsePayload, 'hashIdsSucceeded' | 'hashIdsFailed' | 'responsesFailed'> {
+  extends Omit<ResponsePayload, 'hashIdsFailed' | 'hashIdsSucceeded' | 'responsesFailed'> {
   hashId: string | undefined
 }
 
index 868dd73df07ea4fa37b95a333357c8bf4436a381..d603d0adc360964b80146ef1d1c3d88ff3fd3822 100644 (file)
@@ -2,17 +2,17 @@ export {
   type AutomaticTransactionGeneratorConfiguration,
   type ChargingStationAutomaticTransactionGeneratorConfiguration,
   IdTagDistribution,
-  type Status
+  type Status,
 } from './AutomaticTransactionGenerator.js'
 export type {
   ChargingStationConfiguration,
-  EvseStatusConfiguration
+  EvseStatusConfiguration,
 } from './ChargingStationConfiguration.js'
 export { ChargingStationEvents } from './ChargingStationEvents.js'
 export type { ChargingStationInfo } from './ChargingStationInfo.js'
 export type {
   ChargingStationOcppConfiguration,
-  ConfigurationKey
+  ConfigurationKey,
 } from './ChargingStationOcppConfiguration.js'
 export {
   AmpereUnits,
@@ -21,7 +21,7 @@ export {
   type FirmwareUpgrade,
   PowerUnits,
   Voltage,
-  type WsOptions
+  type WsOptions,
 } from './ChargingStationTemplate.js'
 export {
   type ChargingStationData,
@@ -30,7 +30,7 @@ export {
   type ChargingStationWorkerMessage,
   type ChargingStationWorkerMessageData,
   ChargingStationWorkerMessageEvents,
-  type EvseStatusWorkerType
+  type EvseStatusWorkerType,
 } from './ChargingStationWorker.js'
 export {
   ApplicationProtocolVersion,
@@ -42,7 +42,7 @@ export {
   type StorageConfiguration,
   SupervisionUrlDistribution,
   type UIServerConfiguration,
-  type WorkerConfiguration
+  type WorkerConfiguration,
 } from './ConfigurationData.js'
 export type { ConnectorStatus } from './ConnectorStatus.js'
 export type { EmptyObject } from './EmptyObject.js'
@@ -53,7 +53,7 @@ export type { JsonObject, JsonType } from './JsonType.js'
 export { MapStringifyFormat } from './MapStringifyFormat.js'
 export type {
   MeasurandPerPhaseSampledValueTemplates,
-  SampledValueTemplate
+  SampledValueTemplate,
 } from './MeasurandPerPhaseSampledValueTemplates.js'
 export type { MeasurandValues } from './MeasurandValues.js'
 export { OCPP16ChargePointErrorCode } from './ocpp/1.6/ChargePointErrorCode.js'
@@ -63,11 +63,11 @@ export {
   OCPP16ChargingProfilePurposeType,
   OCPP16ChargingRateUnitType,
   type OCPP16ChargingSchedule,
-  type OCPP16ChargingSchedulePeriod
+  type OCPP16ChargingSchedulePeriod,
 } from './ocpp/1.6/ChargingProfile.js'
 export {
   OCPP16StandardParametersKey,
-  OCPP16SupportedFeatureProfiles
+  OCPP16SupportedFeatureProfiles,
 } from './ocpp/1.6/Configuration.js'
 export { OCPP16DiagnosticsStatus } from './ocpp/1.6/DiagnosticsStatus.js'
 export {
@@ -79,7 +79,7 @@ export {
   type OCPP16MeterValuesRequest,
   type OCPP16MeterValuesResponse,
   OCPP16MeterValueUnit,
-  type OCPP16SampledValue
+  type OCPP16SampledValue,
 } from './ocpp/1.6/MeterValues.js'
 export {
   type ChangeConfigurationRequest,
@@ -92,7 +92,6 @@ export {
   type OCPP16ClearCacheRequest,
   type OCPP16ClearChargingProfileRequest,
   type OCPP16DataTransferRequest,
-  OCPP16DataTransferVendorId,
   type OCPP16DiagnosticsStatusNotificationRequest,
   OCPP16FirmwareStatus,
   type OCPP16FirmwareStatusNotificationRequest,
@@ -109,7 +108,7 @@ export {
   type RemoteStopTransactionRequest,
   type ResetRequest,
   type SetChargingProfileRequest,
-  type UnlockConnectorRequest
+  type UnlockConnectorRequest,
 } from './ocpp/1.6/Requests.js'
 export {
   type ChangeConfigurationResponse,
@@ -130,7 +129,7 @@ export {
   OCPP16TriggerMessageStatus,
   type OCPP16UpdateFirmwareResponse,
   type SetChargingProfileResponse,
-  type UnlockConnectorResponse
+  type UnlockConnectorResponse,
 } from './ocpp/1.6/Responses.js'
 export {
   OCPP16AuthorizationStatus,
@@ -140,7 +139,7 @@ export {
   type OCPP16StartTransactionResponse,
   OCPP16StopTransactionReason,
   type OCPP16StopTransactionRequest,
-  type OCPP16StopTransactionResponse
+  type OCPP16StopTransactionResponse,
 } from './ocpp/1.6/Transaction.js'
 export { BootReasonEnumType, OCPP20ConnectorStatusEnumType } from './ocpp/2.0/Common.js'
 export {
@@ -149,22 +148,23 @@ export {
   type OCPP20HeartbeatRequest,
   OCPP20IncomingRequestCommand,
   OCPP20RequestCommand,
-  type OCPP20StatusNotificationRequest
+  type OCPP20StatusNotificationRequest,
 } from './ocpp/2.0/Requests.js'
 export type {
   OCPP20BootNotificationResponse,
   OCPP20ClearCacheResponse,
   OCPP20HeartbeatResponse,
-  OCPP20StatusNotificationResponse
+  OCPP20StatusNotificationResponse,
 } from './ocpp/2.0/Responses.js'
 export { OCPP20OptionalVariableName } from './ocpp/2.0/Variables.js'
 export { ChargePointErrorCode } from './ocpp/ChargePointErrorCode.js'
 export {
   type ChargingProfile,
   ChargingProfileKindType,
+  ChargingProfilePurposeType,
   ChargingRateUnitType,
   type ChargingSchedulePeriod,
-  RecurrencyKindType
+  RecurrencyKindType,
 } from './ocpp/ChargingProfile.js'
 export { type GenericResponse, GenericStatus, RegistrationStatusEnumType } from './ocpp/Common.js'
 export {
@@ -173,7 +173,7 @@ export {
   type OCPPConfigurationKey,
   StandardParametersKey,
   SupportedFeatureProfiles,
-  VendorParametersKey
+  VendorParametersKey,
 } from './ocpp/Configuration.js'
 export { ConnectorStatusEnum, type ConnectorStatusTransition } from './ocpp/ConnectorStatusEnum.js'
 export { ErrorType } from './ocpp/ErrorType.js'
@@ -185,7 +185,7 @@ export {
   MeterValueMeasurand,
   MeterValuePhase,
   MeterValueUnit,
-  type SampledValue
+  type SampledValue,
 } from './ocpp/MeterValues.js'
 export { OCPPVersion } from './ocpp/OCPPVersion.js'
 export {
@@ -208,12 +208,12 @@ export {
   type RequestParams,
   type ResponseCallback,
   type ResponseType,
-  type StatusNotificationRequest
+  type StatusNotificationRequest,
 } from './ocpp/Requests.js'
 export {
   type Reservation,
   type ReservationKey,
-  ReservationTerminationReason
+  ReservationTerminationReason,
 } from './ocpp/Reservation.js'
 export {
   AvailabilityStatus,
@@ -234,7 +234,7 @@ export {
   type ResponseHandler,
   type StatusNotificationResponse,
   TriggerMessageStatus,
-  UnlockStatus
+  UnlockStatus,
 } from './ocpp/Responses.js'
 export {
   AuthorizationStatus,
@@ -244,7 +244,7 @@ export {
   type StartTransactionResponse,
   StopTransactionReason,
   type StopTransactionRequest,
-  type StopTransactionResponse
+  type StopTransactionResponse,
 } from './ocpp/Transaction.js'
 export { PerformanceRecord } from './orm/entities/PerformanceRecord.js'
 export type { SimulatorState } from './SimulatorState.js'
@@ -252,7 +252,7 @@ export type {
   Statistics,
   StatisticsData,
   TemplateStatistics,
-  TimestampedData
+  TimestampedData,
 } from './Statistics.js'
 export { DBName, StorageType } from './Storage.js'
 export {
@@ -266,12 +266,12 @@ export {
   ProtocolVersion,
   type RequestPayload,
   type ResponsePayload,
-  ResponseStatus
+  ResponseStatus,
 } from './UIProtocol.js'
 export {
   WebSocketCloseEventStatusCode,
   WebSocketCloseEventStatusString,
-  type WSError
+  type WSError,
 } from './WebSocket.js'
 export {
   BroadcastChannelProcedureName,
@@ -279,5 +279,5 @@ export {
   type BroadcastChannelRequestPayload,
   type BroadcastChannelResponse,
   type BroadcastChannelResponsePayload,
-  type MessageEvent
+  type MessageEvent,
 } from './WorkerBroadcastChannel.js'
index db06d0c9da53758e055398531fd549906b0b045a..4255cd059c8a8c22623b08f756a01c29c6145380 100644 (file)
@@ -14,5 +14,5 @@ export enum OCPP16ChargePointErrorCode {
   READER_FAILURE = 'ReaderFailure',
   RESET_FAILURE = 'ResetFailure',
   UNDER_VOLTAGE = 'UnderVoltage',
-  WEAK_SIGNAL = 'WeakSignal'
+  WEAK_SIGNAL = 'WeakSignal',
 }
index ef7467bd4841d3186c32a5d3432243403f29c920..9af1935231ce8041c7e8329b7134a03e7f246482 100644 (file)
@@ -1,11 +1,11 @@
 export enum OCPP16ChargePointStatus {
   Available = 'Available',
-  Preparing = 'Preparing',
   Charging = 'Charging',
-  SuspendedEVSE = 'SuspendedEVSE',
-  SuspendedEV = 'SuspendedEV',
+  Faulted = 'Faulted',
   Finishing = 'Finishing',
+  Preparing = 'Preparing',
   Reserved = 'Reserved',
+  SuspendedEV = 'SuspendedEV',
+  SuspendedEVSE = 'SuspendedEVSE',
   Unavailable = 'Unavailable',
-  Faulted = 'Faulted'
 }
index 744696c586306f52bca08fd23b2d9f28e37db6c2..8a26dbd215b7308c49e41b589d987eb0df45166c 100644 (file)
@@ -1,49 +1,49 @@
 import type { JsonObject } from '../../JsonType.js'
 
+export enum OCPP16ChargingProfileKindType {
+  ABSOLUTE = 'Absolute',
+  RECURRING = 'Recurring',
+  RELATIVE = 'Relative',
+}
+
+export enum OCPP16ChargingProfilePurposeType {
+  CHARGE_POINT_MAX_PROFILE = 'ChargePointMaxProfile',
+  TX_DEFAULT_PROFILE = 'TxDefaultProfile',
+  TX_PROFILE = 'TxProfile',
+}
+
+export enum OCPP16ChargingRateUnitType {
+  AMPERE = 'A',
+  WATT = 'W',
+}
+
+export enum OCPP16RecurrencyKindType {
+  DAILY = 'Daily',
+  WEEKLY = 'Weekly',
+}
+
 export interface OCPP16ChargingProfile extends JsonObject {
   chargingProfileId: number
-  transactionId?: number
-  stackLevel: number
-  chargingProfilePurpose: OCPP16ChargingProfilePurposeType
   chargingProfileKind: OCPP16ChargingProfileKindType
+  chargingProfilePurpose: OCPP16ChargingProfilePurposeType
+  chargingSchedule: OCPP16ChargingSchedule
   recurrencyKind?: OCPP16RecurrencyKindType
+  stackLevel: number
+  transactionId?: number
   validFrom?: Date
   validTo?: Date
-  chargingSchedule: OCPP16ChargingSchedule
 }
 
 export interface OCPP16ChargingSchedule extends JsonObject {
-  startSchedule?: Date
-  duration?: number
   chargingRateUnit: OCPP16ChargingRateUnitType
   chargingSchedulePeriod: OCPP16ChargingSchedulePeriod[]
+  duration?: number
   minChargeRate?: number
+  startSchedule?: Date
 }
 
 export interface OCPP16ChargingSchedulePeriod extends JsonObject {
-  startPeriod: number
   limit: number
   numberPhases?: number
-}
-
-export enum OCPP16ChargingRateUnitType {
-  WATT = 'W',
-  AMPERE = 'A'
-}
-
-export enum OCPP16ChargingProfileKindType {
-  ABSOLUTE = 'Absolute',
-  RECURRING = 'Recurring',
-  RELATIVE = 'Relative'
-}
-
-export enum OCPP16ChargingProfilePurposeType {
-  CHARGE_POINT_MAX_PROFILE = 'ChargePointMaxProfile',
-  TX_DEFAULT_PROFILE = 'TxDefaultProfile',
-  TX_PROFILE = 'TxProfile'
-}
-
-export enum OCPP16RecurrencyKindType {
-  DAILY = 'Daily',
-  WEEKLY = 'Weekly'
+  startPeriod: number
 }
index a5888d93c200567cb6bd4d2cbcffda8d544bac1b..12074b23fffcfb2031fe84f766a32cf7b731267b 100644 (file)
@@ -1,36 +1,36 @@
-export enum OCPP16SupportedFeatureProfiles {
-  Core = 'Core',
-  FirmwareManagement = 'FirmwareManagement',
-  LocalAuthListManagement = 'LocalAuthListManagement',
-  Reservation = 'Reservation',
-  SmartCharging = 'SmartCharging',
-  RemoteTrigger = 'RemoteTrigger'
-}
-
 export enum OCPP16StandardParametersKey {
   AllowOfflineTxForUnknownId = 'AllowOfflineTxForUnknownId',
   AuthorizationCacheEnabled = 'AuthorizationCacheEnabled',
   AuthorizeRemoteTxRequests = 'AuthorizeRemoteTxRequests',
   BlinkRepeat = 'BlinkRepeat',
+  ChargeProfileMaxStackLevel = 'ChargeProfileMaxStackLevel',
+  ChargingScheduleAllowedChargingRateUnit = 'ChargingScheduleAllowedChargingRateUnit',
+  ChargingScheduleMaxPeriods = 'ChargingScheduleMaxPeriods',
   ClockAlignedDataInterval = 'ClockAlignedDataInterval',
   ConnectionTimeOut = 'ConnectionTimeOut',
+  ConnectorPhaseRotation = 'ConnectorPhaseRotation',
+  ConnectorPhaseRotationMaxLength = 'ConnectorPhaseRotationMaxLength',
+  ConnectorSwitch3to1PhaseSupported = 'ConnectorSwitch3to1PhaseSupported',
   GetConfigurationMaxKeys = 'GetConfigurationMaxKeys',
   HeartbeatInterval = 'HeartbeatInterval',
   HeartBeatInterval = 'HeartBeatInterval',
   LightIntensity = 'LightIntensity',
+  LocalAuthListEnabled = 'LocalAuthListEnabled',
+  LocalAuthListMaxLength = 'LocalAuthListMaxLength',
   LocalAuthorizeOffline = 'LocalAuthorizeOffline',
   LocalPreAuthorize = 'LocalPreAuthorize',
+  MaxChargingProfilesInstalled = 'MaxChargingProfilesInstalled',
   MaxEnergyOnInvalidId = 'MaxEnergyOnInvalidId',
   MeterValuesAlignedData = 'MeterValuesAlignedData',
   MeterValuesAlignedDataMaxLength = 'MeterValuesAlignedDataMaxLength',
+  MeterValueSampleInterval = 'MeterValueSampleInterval',
   MeterValuesSampledData = 'MeterValuesSampledData',
   MeterValuesSampledDataMaxLength = 'MeterValuesSampledDataMaxLength',
-  MeterValueSampleInterval = 'MeterValueSampleInterval',
   MinimumStatusDuration = 'MinimumStatusDuration',
   NumberOfConnectors = 'NumberOfConnectors',
+  ReserveConnectorZeroSupported = 'ReserveConnectorZeroSupported',
   ResetRetries = 'ResetRetries',
-  ConnectorPhaseRotation = 'ConnectorPhaseRotation',
-  ConnectorPhaseRotationMaxLength = 'ConnectorPhaseRotationMaxLength',
+  SendLocalListMaxLength = 'SendLocalListMaxLength',
   StopTransactionOnEVSideDisconnect = 'StopTransactionOnEVSideDisconnect',
   StopTransactionOnInvalidId = 'StopTransactionOnInvalidId',
   StopTxnAlignedData = 'StopTxnAlignedData',
@@ -43,17 +43,17 @@ export enum OCPP16StandardParametersKey {
   TransactionMessageRetryInterval = 'TransactionMessageRetryInterval',
   UnlockConnectorOnEVSideDisconnect = 'UnlockConnectorOnEVSideDisconnect',
   WebSocketPingInterval = 'WebSocketPingInterval',
-  LocalAuthListEnabled = 'LocalAuthListEnabled',
-  LocalAuthListMaxLength = 'LocalAuthListMaxLength',
-  SendLocalListMaxLength = 'SendLocalListMaxLength',
-  ReserveConnectorZeroSupported = 'ReserveConnectorZeroSupported',
-  ChargeProfileMaxStackLevel = 'ChargeProfileMaxStackLevel',
-  ChargingScheduleAllowedChargingRateUnit = 'ChargingScheduleAllowedChargingRateUnit',
-  ChargingScheduleMaxPeriods = 'ChargingScheduleMaxPeriods',
-  ConnectorSwitch3to1PhaseSupported = 'ConnectorSwitch3to1PhaseSupported',
-  MaxChargingProfilesInstalled = 'MaxChargingProfilesInstalled'
+}
+
+export enum OCPP16SupportedFeatureProfiles {
+  Core = 'Core',
+  FirmwareManagement = 'FirmwareManagement',
+  LocalAuthListManagement = 'LocalAuthListManagement',
+  RemoteTrigger = 'RemoteTrigger',
+  Reservation = 'Reservation',
+  SmartCharging = 'SmartCharging',
 }
 
 export enum OCPP16VendorParametersKey {
-  ConnectionUrl = 'ConnectionUrl'
+  ConnectionUrl = 'ConnectionUrl',
 }
index c9feccea5f91a34789f9ced610338d6644e2387d..c8c2c8905d5423a8f81df56117ab9211bf9535f4 100644 (file)
@@ -2,5 +2,5 @@ export enum OCPP16DiagnosticsStatus {
   Idle = 'Idle',
   Uploaded = 'Uploaded',
   UploadFailed = 'UploadFailed',
-  Uploading = 'Uploading'
+  Uploading = 'Uploading',
 }
index 92cac7c3245bad2f2952e731727afa689ab4f2a6..9630f0629ff0b8080dcf2569ed1f23666821f8b7 100644 (file)
@@ -1,25 +1,6 @@
 import type { EmptyObject } from '../../EmptyObject.js'
 import type { JsonObject } from '../../JsonType.js'
 
-export enum OCPP16MeterValueUnit {
-  WATT_HOUR = 'Wh',
-  KILO_WATT_HOUR = 'kWh',
-  VAR_HOUR = 'varh',
-  KILO_VAR_HOUR = 'kvarh',
-  WATT = 'W',
-  KILO_WATT = 'kW',
-  VOLT_AMP = 'VA',
-  KILO_VOLT_AMP = 'kVA',
-  VAR = 'var',
-  KILO_VAR = 'kvar',
-  AMP = 'A',
-  VOLT = 'V',
-  TEMP_CELSIUS = 'Celsius',
-  TEMP_FAHRENHEIT = 'Fahrenheit',
-  TEMP_KELVIN = 'K',
-  PERCENT = 'Percent'
-}
-
 export enum OCPP16MeterValueContext {
   INTERRUPTION_BEGIN = 'Interruption.Begin',
   INTERRUPTION_END = 'Interruption.End',
@@ -28,21 +9,30 @@ export enum OCPP16MeterValueContext {
   SAMPLE_PERIODIC = 'Sample.Periodic',
   TRANSACTION_BEGIN = 'Transaction.Begin',
   TRANSACTION_END = 'Transaction.End',
-  TRIGGER = 'Trigger'
+  TRIGGER = 'Trigger',
+}
+
+export enum OCPP16MeterValueLocation {
+  BODY = 'Body',
+  CABLE = 'Cable',
+  EV = 'EV',
+  INLET = 'Inlet',
+  OUTLET = 'Outlet',
 }
 
 export enum OCPP16MeterValueMeasurand {
   CURRENT_EXPORT = 'Current.Export',
   CURRENT_IMPORT = 'Current.Import',
   CURRENT_OFFERED = 'Current.Offered',
-  ENERGY_ACTIVE_EXPORT_REGISTER = 'Energy.Active.Export.Register',
-  ENERGY_ACTIVE_IMPORT_REGISTER = 'Energy.Active.Import.Register',
-  ENERGY_REACTIVE_EXPORT_REGISTER = 'Energy.Reactive.Export.Register',
-  ENERGY_REACTIVE_IMPORT_REGISTER = 'Energy.Reactive.Import.Register',
   ENERGY_ACTIVE_EXPORT_INTERVAL = 'Energy.Active.Export.Interval',
+  ENERGY_ACTIVE_EXPORT_REGISTER = 'Energy.Active.Export.Register',
   ENERGY_ACTIVE_IMPORT_INTERVAL = 'Energy.Active.Import.Interval',
+  ENERGY_ACTIVE_IMPORT_REGISTER = 'Energy.Active.Import.Register',
   ENERGY_REACTIVE_EXPORT_INTERVAL = 'Energy.Reactive.Export.Interval',
+  ENERGY_REACTIVE_EXPORT_REGISTER = 'Energy.Reactive.Export.Register',
   ENERGY_REACTIVE_IMPORT_INTERVAL = 'Energy.Reactive.Import.Interval',
+  ENERGY_REACTIVE_IMPORT_REGISTER = 'Energy.Reactive.Import.Register',
+  FAN_RPM = 'RPM',
   FREQUENCY = 'Frequency',
   POWER_ACTIVE_EXPORT = 'Power.Active.Export',
   POWER_ACTIVE_IMPORT = 'Power.Active.Import',
@@ -50,57 +40,67 @@ export enum OCPP16MeterValueMeasurand {
   POWER_OFFERED = 'Power.Offered',
   POWER_REACTIVE_EXPORT = 'Power.Reactive.Export',
   POWER_REACTIVE_IMPORT = 'Power.Reactive.Import',
-  FAN_RPM = 'RPM',
   STATE_OF_CHARGE = 'SoC',
   TEMPERATURE = 'Temperature',
-  VOLTAGE = 'Voltage'
-}
-
-export enum OCPP16MeterValueLocation {
-  BODY = 'Body',
-  CABLE = 'Cable',
-  EV = 'EV',
-  INLET = 'Inlet',
-  OUTLET = 'Outlet'
+  VOLTAGE = 'Voltage',
 }
 
 export enum OCPP16MeterValuePhase {
   L1 = 'L1',
-  L2 = 'L2',
-  L3 = 'L3',
-  N = 'N',
+  L1_L2 = 'L1-L2',
   L1_N = 'L1-N',
+  L2 = 'L2',
+  L2_L3 = 'L2-L3',
   L2_N = 'L2-N',
+  L3 = 'L3',
+  L3_L1 = 'L3-L1',
   L3_N = 'L3-N',
-  L1_L2 = 'L1-L2',
-  L2_L3 = 'L2-L3',
-  L3_L1 = 'L3-L1'
+  N = 'N',
 }
 
-enum OCPP16MeterValueFormat {
-  RAW = 'Raw',
-  SIGNED_DATA = 'SignedData'
+export enum OCPP16MeterValueUnit {
+  AMP = 'A',
+  KILO_VAR = 'kvar',
+  KILO_VAR_HOUR = 'kvarh',
+  KILO_VOLT_AMP = 'kVA',
+  KILO_WATT = 'kW',
+  KILO_WATT_HOUR = 'kWh',
+  PERCENT = 'Percent',
+  TEMP_CELSIUS = 'Celsius',
+  TEMP_FAHRENHEIT = 'Fahrenheit',
+  TEMP_KELVIN = 'K',
+  VAR = 'var',
+  VAR_HOUR = 'varh',
+  VOLT = 'V',
+  VOLT_AMP = 'VA',
+  WATT = 'W',
+  WATT_HOUR = 'Wh',
 }
 
-export interface OCPP16SampledValue extends JsonObject {
-  value: string
-  unit?: OCPP16MeterValueUnit
-  context?: OCPP16MeterValueContext
-  measurand?: OCPP16MeterValueMeasurand
-  phase?: OCPP16MeterValuePhase
-  location?: OCPP16MeterValueLocation
-  format?: OCPP16MeterValueFormat
+enum OCPP16MeterValueFormat {
+  RAW = 'Raw',
+  SIGNED_DATA = 'SignedData',
 }
 
 export interface OCPP16MeterValue extends JsonObject {
-  timestamp: Date
   sampledValue: OCPP16SampledValue[]
+  timestamp: Date
 }
 
 export interface OCPP16MeterValuesRequest extends JsonObject {
   connectorId: number
-  transactionId?: number
   meterValue: OCPP16MeterValue[]
+  transactionId?: number
 }
 
 export type OCPP16MeterValuesResponse = EmptyObject
+
+export interface OCPP16SampledValue extends JsonObject {
+  context?: OCPP16MeterValueContext
+  format?: OCPP16MeterValueFormat
+  location?: OCPP16MeterValueLocation
+  measurand?: OCPP16MeterValueMeasurand
+  phase?: OCPP16MeterValuePhase
+  unit?: OCPP16MeterValueUnit
+  value: string
+}
index c57be2032751277115f298947575d7877457d4cd..e4283183b7455e9b27cedbefd4cd494022fbe96d 100644 (file)
@@ -5,118 +5,104 @@ import type { OCPP16ChargePointStatus } from './ChargePointStatus.js'
 import type {
   OCPP16ChargingProfile,
   OCPP16ChargingProfilePurposeType,
-  OCPP16ChargingRateUnitType
+  OCPP16ChargingRateUnitType,
 } from './ChargingProfile.js'
 import type { OCPP16StandardParametersKey, OCPP16VendorParametersKey } from './Configuration.js'
 import type { OCPP16DiagnosticsStatus } from './DiagnosticsStatus.js'
 
-export enum OCPP16RequestCommand {
-  BOOT_NOTIFICATION = 'BootNotification',
-  HEARTBEAT = 'Heartbeat',
-  STATUS_NOTIFICATION = 'StatusNotification',
-  AUTHORIZE = 'Authorize',
-  START_TRANSACTION = 'StartTransaction',
-  STOP_TRANSACTION = 'StopTransaction',
-  METER_VALUES = 'MeterValues',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
-  FIRMWARE_STATUS_NOTIFICATION = 'FirmwareStatusNotification',
-  DATA_TRANSFER = 'DataTransfer'
+export enum OCPP16AvailabilityType {
+  Inoperative = 'Inoperative',
+  Operative = 'Operative',
+}
+
+export enum OCPP16FirmwareStatus {
+  Downloaded = 'Downloaded',
+  DownloadFailed = 'DownloadFailed',
+  Downloading = 'Downloading',
+  Idle = 'Idle',
+  InstallationFailed = 'InstallationFailed',
+  Installed = 'Installed',
+  Installing = 'Installing',
 }
 
 export enum OCPP16IncomingRequestCommand {
-  RESET = 'Reset',
-  CLEAR_CACHE = 'ClearCache',
+  CANCEL_RESERVATION = 'CancelReservation',
   CHANGE_AVAILABILITY = 'ChangeAvailability',
-  UNLOCK_CONNECTOR = 'UnlockConnector',
-  GET_CONFIGURATION = 'GetConfiguration',
   CHANGE_CONFIGURATION = 'ChangeConfiguration',
-  GET_COMPOSITE_SCHEDULE = 'GetCompositeSchedule',
-  SET_CHARGING_PROFILE = 'SetChargingProfile',
+  CLEAR_CACHE = 'ClearCache',
   CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+  DATA_TRANSFER = 'DataTransfer',
+  GET_COMPOSITE_SCHEDULE = 'GetCompositeSchedule',
+  GET_CONFIGURATION = 'GetConfiguration',
+  GET_DIAGNOSTICS = 'GetDiagnostics',
   REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
   REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
-  GET_DIAGNOSTICS = 'GetDiagnostics',
+  RESERVE_NOW = 'ReserveNow',
+  RESET = 'Reset',
+  SET_CHARGING_PROFILE = 'SetChargingProfile',
   TRIGGER_MESSAGE = 'TriggerMessage',
-  DATA_TRANSFER = 'DataTransfer',
+  UNLOCK_CONNECTOR = 'UnlockConnector',
   UPDATE_FIRMWARE = 'UpdateFirmware',
-  RESERVE_NOW = 'ReserveNow',
-  CANCEL_RESERVATION = 'CancelReservation'
 }
 
-export type OCPP16HeartbeatRequest = EmptyObject
-
-export interface OCPP16BootNotificationRequest extends JsonObject {
-  chargePointVendor: string
-  chargePointModel: string
-  chargePointSerialNumber?: string
-  chargeBoxSerialNumber?: string
-  firmwareVersion?: string
-  iccid?: string
-  imsi?: string
-  meterType?: string
-  meterSerialNumber?: string
+export enum OCPP16MessageTrigger {
+  BootNotification = 'BootNotification',
+  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
+  FirmwareStatusNotification = 'FirmwareStatusNotification',
+  Heartbeat = 'Heartbeat',
+  MeterValues = 'MeterValues',
+  StatusNotification = 'StatusNotification',
 }
 
-export interface OCPP16StatusNotificationRequest extends JsonObject {
-  connectorId: number
-  errorCode: OCPP16ChargePointErrorCode
-  status: OCPP16ChargePointStatus
-  info?: string
-  timestamp?: Date
-  vendorId?: string
-  vendorErrorCode?: string
+export enum OCPP16RequestCommand {
+  AUTHORIZE = 'Authorize',
+  BOOT_NOTIFICATION = 'BootNotification',
+  DATA_TRANSFER = 'DataTransfer',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
+  FIRMWARE_STATUS_NOTIFICATION = 'FirmwareStatusNotification',
+  HEARTBEAT = 'Heartbeat',
+  METER_VALUES = 'MeterValues',
+  START_TRANSACTION = 'StartTransaction',
+  STATUS_NOTIFICATION = 'StatusNotification',
+  STOP_TRANSACTION = 'StopTransaction',
 }
 
-export type OCPP16ClearCacheRequest = EmptyObject
-
-type OCPP16ConfigurationKey = string | OCPP16StandardParametersKey | OCPP16VendorParametersKey
+enum ResetType {
+  HARD = 'Hard',
+  SOFT = 'Soft',
+}
 
 export interface ChangeConfigurationRequest extends JsonObject {
   key: OCPP16ConfigurationKey
   value: string
 }
 
-export interface RemoteStartTransactionRequest extends JsonObject {
-  connectorId: number
-  idTag: string
-  chargingProfile?: OCPP16ChargingProfile
-}
-
-export interface RemoteStopTransactionRequest extends JsonObject {
-  transactionId: number
-}
-
-export interface UnlockConnectorRequest extends JsonObject {
-  connectorId: number
-}
-
 export interface GetConfigurationRequest extends JsonObject {
   key?: OCPP16ConfigurationKey[]
 }
 
-enum ResetType {
-  HARD = 'Hard',
-  SOFT = 'Soft'
-}
-
-export interface ResetRequest extends JsonObject {
-  type: ResetType
-}
-
-export interface OCPP16GetCompositeScheduleRequest extends JsonObject {
-  connectorId: number
-  duration: number
-  chargingRateUnit?: OCPP16ChargingRateUnitType
+export interface GetDiagnosticsRequest extends JsonObject {
+  location: string
+  retries?: number
+  retryInterval?: number
+  startTime?: Date
+  stopTime?: Date
 }
 
-export interface SetChargingProfileRequest extends JsonObject {
-  connectorId: number
-  csChargingProfiles: OCPP16ChargingProfile
+export interface OCPP16BootNotificationRequest extends JsonObject {
+  chargeBoxSerialNumber?: string
+  chargePointModel: string
+  chargePointSerialNumber?: string
+  chargePointVendor: string
+  firmwareVersion?: string
+  iccid?: string
+  imsi?: string
+  meterSerialNumber?: string
+  meterType?: string
 }
 
-export enum OCPP16AvailabilityType {
-  Inoperative = 'Inoperative',
-  Operative = 'Operative'
+export interface OCPP16CancelReservationRequest extends JsonObject {
+  reservationId: number
 }
 
 export interface OCPP16ChangeAvailabilityRequest extends JsonObject {
@@ -124,76 +110,88 @@ export interface OCPP16ChangeAvailabilityRequest extends JsonObject {
   type: OCPP16AvailabilityType
 }
 
+export type OCPP16ClearCacheRequest = EmptyObject
+
 export interface OCPP16ClearChargingProfileRequest extends JsonObject {
-  id?: number
-  connectorId?: number
   chargingProfilePurpose?: OCPP16ChargingProfilePurposeType
+  connectorId?: number
+  id?: number
   stackLevel?: number
 }
 
-export interface OCPP16UpdateFirmwareRequest extends JsonObject {
-  location: string
-  retrieveDate: Date
-  retries?: number
-  retryInterval?: number
+export interface OCPP16DataTransferRequest extends JsonObject {
+  data?: string
+  messageId?: string
+  vendorId: string
 }
 
-export enum OCPP16FirmwareStatus {
-  Downloaded = 'Downloaded',
-  DownloadFailed = 'DownloadFailed',
-  Downloading = 'Downloading',
-  Idle = 'Idle',
-  InstallationFailed = 'InstallationFailed',
-  Installing = 'Installing',
-  Installed = 'Installed'
+export interface OCPP16DiagnosticsStatusNotificationRequest extends JsonObject {
+  status: OCPP16DiagnosticsStatus
 }
 
 export interface OCPP16FirmwareStatusNotificationRequest extends JsonObject {
   status: OCPP16FirmwareStatus
 }
 
-export interface GetDiagnosticsRequest extends JsonObject {
-  location: string
-  retries?: number
-  retryInterval?: number
-  startTime?: Date
-  stopTime?: Date
+export interface OCPP16GetCompositeScheduleRequest extends JsonObject {
+  chargingRateUnit?: OCPP16ChargingRateUnitType
+  connectorId: number
+  duration: number
 }
 
-export interface OCPP16DiagnosticsStatusNotificationRequest extends JsonObject {
-  status: OCPP16DiagnosticsStatus
+export type OCPP16HeartbeatRequest = EmptyObject
+
+export interface OCPP16ReserveNowRequest extends JsonObject {
+  connectorId: number
+  expiryDate: Date
+  idTag: string
+  parentIdTag?: string
+  reservationId: number
 }
 
-export enum OCPP16MessageTrigger {
-  BootNotification = 'BootNotification',
-  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
-  FirmwareStatusNotification = 'FirmwareStatusNotification',
-  Heartbeat = 'Heartbeat',
-  MeterValues = 'MeterValues',
-  StatusNotification = 'StatusNotification'
+export interface OCPP16StatusNotificationRequest extends JsonObject {
+  connectorId: number
+  errorCode: OCPP16ChargePointErrorCode
+  info?: string
+  status: OCPP16ChargePointStatus
+  timestamp?: Date
+  vendorErrorCode?: string
+  vendorId?: string
 }
 
 export interface OCPP16TriggerMessageRequest extends JsonObject {
+  connectorId?: number
   requestedMessage: OCPP16MessageTrigger
+}
+
+export interface OCPP16UpdateFirmwareRequest extends JsonObject {
+  location: string
+  retries?: number
+  retrieveDate: Date
+  retryInterval?: number
+}
+
+export interface RemoteStartTransactionRequest extends JsonObject {
+  chargingProfile?: OCPP16ChargingProfile
   connectorId?: number
+  idTag: string
 }
 
-export enum OCPP16DataTransferVendorId {}
+export interface RemoteStopTransactionRequest extends JsonObject {
+  transactionId: number
+}
 
-export interface OCPP16DataTransferRequest extends JsonObject {
-  vendorId: string
-  messageId?: string
-  data?: string
+export interface ResetRequest extends JsonObject {
+  type: ResetType
 }
 
-export interface OCPP16ReserveNowRequest extends JsonObject {
+export interface SetChargingProfileRequest extends JsonObject {
   connectorId: number
-  expiryDate: Date
-  idTag: string
-  parentIdTag?: string
-  reservationId: number
+  csChargingProfiles: OCPP16ChargingProfile
 }
 
-export interface OCPP16CancelReservationRequest extends JsonObject {
-  reservationId: number
+export interface UnlockConnectorRequest extends JsonObject {
+  connectorId: number
 }
+
+type OCPP16ConfigurationKey = OCPP16StandardParametersKey | OCPP16VendorParametersKey | string
index 8b67a9267d5a3a9da03632a0bd7b20a381434102..6dc0358065370752ea7f59edcb021ac15043336a 100644 (file)
@@ -4,121 +4,121 @@ import type { GenericStatus, RegistrationStatusEnumType } from '../Common.js'
 import type { OCPPConfigurationKey } from '../Configuration.js'
 import type { OCPP16ChargingSchedule } from './ChargingProfile.js'
 
-export interface OCPP16HeartbeatResponse extends JsonObject {
-  currentTime: Date
+export enum OCPP16AvailabilityStatus {
+  ACCEPTED = 'Accepted',
+  REJECTED = 'Rejected',
+  SCHEDULED = 'Scheduled',
 }
 
-export enum OCPP16UnlockStatus {
-  UNLOCKED = 'Unlocked',
-  UNLOCK_FAILED = 'UnlockFailed',
-  NOT_SUPPORTED = 'NotSupported'
+export enum OCPP16ChargingProfileStatus {
+  ACCEPTED = 'Accepted',
+  NOT_SUPPORTED = 'NotSupported',
+  REJECTED = 'Rejected',
 }
 
-export interface UnlockConnectorResponse extends JsonObject {
-  status: OCPP16UnlockStatus
+export enum OCPP16ClearChargingProfileStatus {
+  ACCEPTED = 'Accepted',
+  UNKNOWN = 'Unknown',
 }
 
 export enum OCPP16ConfigurationStatus {
   ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
+  NOT_SUPPORTED = 'NotSupported',
   REBOOT_REQUIRED = 'RebootRequired',
-  NOT_SUPPORTED = 'NotSupported'
+  REJECTED = 'Rejected',
 }
 
-export interface ChangeConfigurationResponse extends JsonObject {
-  status: OCPP16ConfigurationStatus
+export enum OCPP16DataTransferStatus {
+  ACCEPTED = 'Accepted',
+  REJECTED = 'Rejected',
+  UNKNOWN_MESSAGE_ID = 'UnknownMessageId',
+  UNKNOWN_VENDOR_ID = 'UnknownVendorId',
 }
 
-export interface OCPP16BootNotificationResponse extends JsonObject {
-  status: RegistrationStatusEnumType
-  currentTime: Date
-  interval: number
+export enum OCPP16ReservationStatus {
+  ACCEPTED = 'Accepted',
+  FAULTED = 'Faulted',
+  NOT_SUPPORTED = 'NotSupported',
+  OCCUPIED = 'Occupied',
+  REJECTED = 'Rejected',
+  UNAVAILABLE = 'Unavailable',
 }
 
-export type OCPP16StatusNotificationResponse = EmptyObject
+export enum OCPP16TriggerMessageStatus {
+  ACCEPTED = 'Accepted',
+  NOT_IMPLEMENTED = 'NotImplemented',
+  REJECTED = 'Rejected',
+}
 
-export interface GetConfigurationResponse extends JsonObject {
-  configurationKey: OCPPConfigurationKey[]
-  unknownKey: string[]
+export enum OCPP16UnlockStatus {
+  NOT_SUPPORTED = 'NotSupported',
+  UNLOCK_FAILED = 'UnlockFailed',
+  UNLOCKED = 'Unlocked',
 }
 
-export enum OCPP16ChargingProfileStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  NOT_SUPPORTED = 'NotSupported'
+export interface ChangeConfigurationResponse extends JsonObject {
+  status: OCPP16ConfigurationStatus
 }
 
-export interface OCPP16GetCompositeScheduleResponse extends JsonObject {
-  status: GenericStatus
-  connectorId?: number
-  scheduleStart?: Date
-  chargingSchedule?: OCPP16ChargingSchedule
+export interface GetConfigurationResponse extends JsonObject {
+  configurationKey: OCPPConfigurationKey[]
+  unknownKey: string[]
 }
 
-export interface SetChargingProfileResponse extends JsonObject {
-  status: OCPP16ChargingProfileStatus
+export interface GetDiagnosticsResponse extends JsonObject {
+  fileName?: string
 }
 
-export enum OCPP16AvailabilityStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  SCHEDULED = 'Scheduled'
+export interface OCPP16BootNotificationResponse extends JsonObject {
+  currentTime: Date
+  interval: number
+  status: RegistrationStatusEnumType
 }
 
 export interface OCPP16ChangeAvailabilityResponse extends JsonObject {
   status: OCPP16AvailabilityStatus
 }
 
-export enum OCPP16ClearChargingProfileStatus {
-  ACCEPTED = 'Accepted',
-  UNKNOWN = 'Unknown'
-}
-
 export interface OCPP16ClearChargingProfileResponse extends JsonObject {
   status: OCPP16ClearChargingProfileStatus
 }
 
-export type OCPP16UpdateFirmwareResponse = EmptyObject
+export interface OCPP16DataTransferResponse extends JsonObject {
+  data?: string
+  status: OCPP16DataTransferStatus
+}
+
+export type OCPP16DiagnosticsStatusNotificationResponse = EmptyObject
 
 export type OCPP16FirmwareStatusNotificationResponse = EmptyObject
 
-export interface GetDiagnosticsResponse extends JsonObject {
-  fileName?: string
+export interface OCPP16GetCompositeScheduleResponse extends JsonObject {
+  chargingSchedule?: OCPP16ChargingSchedule
+  connectorId?: number
+  scheduleStart?: Date
+  status: GenericStatus
 }
 
-export type OCPP16DiagnosticsStatusNotificationResponse = EmptyObject
+export interface OCPP16HeartbeatResponse extends JsonObject {
+  currentTime: Date
+}
 
-export enum OCPP16TriggerMessageStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  NOT_IMPLEMENTED = 'NotImplemented'
+export interface OCPP16ReserveNowResponse extends JsonObject {
+  status: OCPP16ReservationStatus
 }
 
+export type OCPP16StatusNotificationResponse = EmptyObject
+
 export interface OCPP16TriggerMessageResponse extends JsonObject {
   status: OCPP16TriggerMessageStatus
 }
 
-export enum OCPP16DataTransferStatus {
-  ACCEPTED = 'Accepted',
-  REJECTED = 'Rejected',
-  UNKNOWN_MESSAGE_ID = 'UnknownMessageId',
-  UNKNOWN_VENDOR_ID = 'UnknownVendorId'
-}
-
-export interface OCPP16DataTransferResponse extends JsonObject {
-  status: OCPP16DataTransferStatus
-  data?: string
-}
+export type OCPP16UpdateFirmwareResponse = EmptyObject
 
-export enum OCPP16ReservationStatus {
-  ACCEPTED = 'Accepted',
-  FAULTED = 'Faulted',
-  OCCUPIED = 'Occupied',
-  REJECTED = 'Rejected',
-  UNAVAILABLE = 'Unavailable',
-  NOT_SUPPORTED = 'NotSupported'
+export interface SetChargingProfileResponse extends JsonObject {
+  status: OCPP16ChargingProfileStatus
 }
 
-export interface OCPP16ReserveNowResponse extends JsonObject {
-  status: OCPP16ReservationStatus
+export interface UnlockConnectorResponse extends JsonObject {
+  status: OCPP16UnlockStatus
 }
index f9659f695398822e3932197137b8ee581626d6d2..2aa5db38e8d5663a64ec27134c7cca0d83a137b2 100644 (file)
@@ -1,7 +1,16 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { OCPP16MeterValue } from './MeterValues.js'
 
+export enum OCPP16AuthorizationStatus {
+  ACCEPTED = 'Accepted',
+  BLOCKED = 'Blocked',
+  CONCURRENT_TX = 'ConcurrentTx',
+  EXPIRED = 'Expired',
+  INVALID = 'Invalid',
+}
+
 export enum OCPP16StopTransactionReason {
+  DE_AUTHORIZED = 'DeAuthorized',
   EMERGENCY_STOP = 'EmergencyStop',
   EV_DISCONNECTED = 'EVDisconnected',
   HARD_RESET = 'HardReset',
@@ -12,21 +21,6 @@ export enum OCPP16StopTransactionReason {
   REMOTE = 'Remote',
   SOFT_RESET = 'SoftReset',
   UNLOCK_COMMAND = 'UnlockCommand',
-  DE_AUTHORIZED = 'DeAuthorized'
-}
-
-export enum OCPP16AuthorizationStatus {
-  ACCEPTED = 'Accepted',
-  BLOCKED = 'Blocked',
-  EXPIRED = 'Expired',
-  INVALID = 'Invalid',
-  CONCURRENT_TX = 'ConcurrentTx'
-}
-
-interface IdTagInfo extends JsonObject {
-  status: OCPP16AuthorizationStatus
-  parentIdTag?: string
-  expiryDate?: Date
 }
 
 export interface OCPP16AuthorizeRequest extends JsonObject {
@@ -41,8 +35,8 @@ export interface OCPP16StartTransactionRequest extends JsonObject {
   connectorId: number
   idTag: string
   meterStart: number
-  timestamp: Date
   reservationId?: number
+  timestamp: Date
 }
 
 export interface OCPP16StartTransactionResponse extends JsonObject {
@@ -53,12 +47,18 @@ export interface OCPP16StartTransactionResponse extends JsonObject {
 export interface OCPP16StopTransactionRequest extends JsonObject {
   idTag?: string
   meterStop: number
-  timestamp: Date
-  transactionId: number
   reason?: OCPP16StopTransactionReason
+  timestamp: Date
   transactionData?: OCPP16MeterValue[]
+  transactionId: number
 }
 
 export interface OCPP16StopTransactionResponse extends JsonObject {
   idTagInfo?: IdTagInfo
 }
+
+interface IdTagInfo extends JsonObject {
+  expiryDate?: Date
+  parentIdTag?: string
+  status: OCPP16AuthorizationStatus
+}
index 785c604646654b509a803ff4ec57140a397a7a38..1bd577bedb4a585141f946f367d483fed203b5b6 100644 (file)
@@ -1,17 +1,6 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { GenericStatus } from '../Common.js'
 
-export enum DataEnumType {
-  string = 'string',
-  decimal = 'decimal',
-  integer = 'integer',
-  dateTime = 'dateTime',
-  boolean = 'boolean',
-  OptionList = 'OptionList',
-  SequenceList = 'SequenceList',
-  MemberList = 'MemberList'
-}
-
 export enum BootReasonEnumType {
   ApplicationReset = 'ApplicationReset',
   FirmwareUpdate = 'FirmwareUpdate',
@@ -21,106 +10,117 @@ export enum BootReasonEnumType {
   ScheduledReset = 'ScheduledReset',
   Triggered = 'Triggered',
   Unknown = 'Unknown',
-  Watchdog = 'Watchdog'
+  Watchdog = 'Watchdog',
 }
 
-export enum OperationalStatusEnumType {
-  Operative = 'Operative',
-  Inoperative = 'Inoperative'
+export enum CertificateActionEnumType {
+  Install = 'Install',
+  Update = 'Update',
 }
 
-export enum OCPP20ConnectorStatusEnumType {
-  Available = 'Available',
-  Occupied = 'Occupied',
-  Reserved = 'Reserved',
-  Unavailable = 'Unavailable',
-  Faulted = 'Faulted'
+export enum CertificateSigningUseEnumType {
+  ChargingStationCertificate = 'ChargingStationCertificate',
+  V2GCertificate = 'V2GCertificate',
 }
 
-export type GenericStatusEnumType = GenericStatus
+export enum DataEnumType {
+  boolean = 'boolean',
+  dateTime = 'dateTime',
+  decimal = 'decimal',
+  integer = 'integer',
+  MemberList = 'MemberList',
+  OptionList = 'OptionList',
+  SequenceList = 'SequenceList',
+  string = 'string',
+}
 
-export enum HashAlgorithmEnumType {
-  SHA256 = 'SHA256',
-  SHA384 = 'SHA384',
-  SHA512 = 'SHA512'
+export enum DeleteCertificateStatusEnumType {
+  Accepted = 'Accepted',
+  Failed = 'Failed',
+  NotFound = 'NotFound',
 }
 
 export enum GetCertificateIdUseEnumType {
-  V2GRootCertificate = 'V2GRootCertificate',
-  MORootCertificate = 'MORootCertificate',
   CSMSRootCertificate = 'CSMSRootCertificate',
+  ManufacturerRootCertificate = 'ManufacturerRootCertificate',
+  MORootCertificate = 'MORootCertificate',
   V2GCertificateChain = 'V2GCertificateChain',
-  ManufacturerRootCertificate = 'ManufacturerRootCertificate'
+  V2GRootCertificate = 'V2GRootCertificate',
 }
 
 export enum GetCertificateStatusEnumType {
   Accepted = 'Accepted',
-  Failed = 'Failed'
+  Failed = 'Failed',
 }
 
 export enum GetInstalledCertificateStatusEnumType {
   Accepted = 'Accepted',
-  NotFound = 'NotFound'
+  NotFound = 'NotFound',
+}
+
+export enum HashAlgorithmEnumType {
+  SHA256 = 'SHA256',
+  SHA384 = 'SHA384',
+  SHA512 = 'SHA512',
 }
 
 export enum InstallCertificateStatusEnumType {
   Accepted = 'Accepted',
+  Failed = 'Failed',
   Rejected = 'Rejected',
-  Failed = 'Failed'
 }
 
 export enum InstallCertificateUseEnumType {
-  V2GRootCertificate = 'V2GRootCertificate',
-  MORootCertificate = 'MORootCertificate',
   CSMSRootCertificate = 'CSMSRootCertificate',
-  ManufacturerRootCertificate = 'ManufacturerRootCertificate'
+  ManufacturerRootCertificate = 'ManufacturerRootCertificate',
+  MORootCertificate = 'MORootCertificate',
+  V2GRootCertificate = 'V2GRootCertificate',
 }
 
-export enum DeleteCertificateStatusEnumType {
-  Accepted = 'Accepted',
-  Failed = 'Failed',
-  NotFound = 'NotFound'
+export enum OCPP20ConnectorStatusEnumType {
+  Available = 'Available',
+  Faulted = 'Faulted',
+  Occupied = 'Occupied',
+  Reserved = 'Reserved',
+  Unavailable = 'Unavailable',
 }
 
-export enum CertificateActionEnumType {
-  Install = 'Install',
-  Update = 'Update'
+export enum OperationalStatusEnumType {
+  Inoperative = 'Inoperative',
+  Operative = 'Operative',
 }
 
-export enum CertificateSigningUseEnumType {
-  ChargingStationCertificate = 'ChargingStationCertificate',
-  V2GCertificate = 'V2GCertificate'
+export interface CertificateHashDataChainType extends JsonObject {
+  certificateHashData: CertificateHashDataType
+  certificateType: GetCertificateIdUseEnumType
+  childCertificateHashData?: CertificateHashDataType
 }
 
-export type CertificateSignedStatusEnumType = GenericStatusEnumType
-
 export interface CertificateHashDataType extends JsonObject {
   hashAlgorithm: HashAlgorithmEnumType
-  issuerNameHash: string
   issuerKeyHash: string
+  issuerNameHash: string
   serialNumber: string
 }
 
-export interface CertificateHashDataChainType extends JsonObject {
-  certificateType: GetCertificateIdUseEnumType
-  certificateHashData: CertificateHashDataType
-  childCertificateHashData?: CertificateHashDataType
+export type CertificateSignedStatusEnumType = GenericStatusEnumType
+
+export interface EVSEType extends JsonObject {
+  connectorId?: string
+  id: number
 }
 
+export type GenericStatusEnumType = GenericStatus
+
 export interface OCSPRequestDataType extends JsonObject {
   hashAlgorithm: HashAlgorithmEnumType
-  issuerNameHash: string
   issuerKeyHash: string
-  serialNumber: string
+  issuerNameHash: string
   responderURL: string
+  serialNumber: string
 }
 
 export interface StatusInfoType extends JsonObject {
-  reasonCode: string
   additionalInfo?: string
-}
-
-export interface EVSEType extends JsonObject {
-  id: number
-  connectorId?: string
+  reasonCode: string
 }
index f075e642f7284f657398f5e3a7a46b0ab7f565db..0cf30f92916921be5e28319d036f8acd62d2a38b 100644 (file)
@@ -3,56 +3,56 @@ import type { JsonObject } from '../../JsonType.js'
 import type {
   BootReasonEnumType,
   InstallCertificateUseEnumType,
-  OCPP20ConnectorStatusEnumType
+  OCPP20ConnectorStatusEnumType,
 } from './Common.js'
 import type { OCPP20SetVariableDataType } from './Variables.js'
 
-export enum OCPP20RequestCommand {
-  BOOT_NOTIFICATION = 'BootNotification',
-  HEARTBEAT = 'Heartbeat',
-  STATUS_NOTIFICATION = 'StatusNotification'
-}
-
 export enum OCPP20IncomingRequestCommand {
   CLEAR_CACHE = 'ClearCache',
   REQUEST_START_TRANSACTION = 'RequestStartTransaction',
-  REQUEST_STOP_TRANSACTION = 'RequestStopTransaction'
+  REQUEST_STOP_TRANSACTION = 'RequestStopTransaction',
 }
 
-interface ModemType extends JsonObject {
-  iccid?: string
-  imsi?: string
-}
-
-interface ChargingStationType extends JsonObject {
-  serialNumber?: string
-  model: string
-  vendorName: string
-  firmwareVersion?: string
-  modem?: ModemType
+export enum OCPP20RequestCommand {
+  BOOT_NOTIFICATION = 'BootNotification',
+  HEARTBEAT = 'Heartbeat',
+  STATUS_NOTIFICATION = 'StatusNotification',
 }
 
 export interface OCPP20BootNotificationRequest extends JsonObject {
-  reason: BootReasonEnumType
   chargingStation: ChargingStationType
+  reason: BootReasonEnumType
 }
 
+export type OCPP20ClearCacheRequest = EmptyObject
+
 export type OCPP20HeartbeatRequest = EmptyObject
 
-export type OCPP20ClearCacheRequest = EmptyObject
+export interface OCPP20InstallCertificateRequest extends JsonObject {
+  certificate: string
+  certificateType: InstallCertificateUseEnumType
+}
+
+export interface OCPP20SetVariablesRequest extends JsonObject {
+  setVariableData: OCPP20SetVariableDataType[]
+}
 
 export interface OCPP20StatusNotificationRequest extends JsonObject {
-  timestamp: Date
+  connectorId: number
   connectorStatus: OCPP20ConnectorStatusEnumType
   evseId: number
-  connectorId: number
+  timestamp: Date
 }
 
-export interface OCPP20SetVariablesRequest extends JsonObject {
-  setVariableData: OCPP20SetVariableDataType[]
+interface ChargingStationType extends JsonObject {
+  firmwareVersion?: string
+  model: string
+  modem?: ModemType
+  serialNumber?: string
+  vendorName: string
 }
 
-export interface OCPP20InstallCertificateRequest extends JsonObject {
-  certificateType: InstallCertificateUseEnumType
-  certificate: string
+interface ModemType extends JsonObject {
+  iccid?: string
+  imsi?: string
 }
index 4248c5d8f6baf74bc7b5874371c6026a781e9442..e735c788ff8cd1b835a844ea6713f4c11cb2eea8 100644 (file)
@@ -4,33 +4,33 @@ import type { RegistrationStatusEnumType } from '../Common.js'
 import type {
   GenericStatusEnumType,
   InstallCertificateStatusEnumType,
-  StatusInfoType
+  StatusInfoType,
 } from './Common.js'
 import type { OCPP20SetVariableResultType } from './Variables.js'
 
 export interface OCPP20BootNotificationResponse extends JsonObject {
   currentTime: Date
-  status: RegistrationStatusEnumType
   interval: number
+  status: RegistrationStatusEnumType
   statusInfo?: StatusInfoType
 }
 
-export interface OCPP20HeartbeatResponse extends JsonObject {
-  currentTime: Date
-}
-
 export interface OCPP20ClearCacheResponse extends JsonObject {
   status: GenericStatusEnumType
   statusInfo?: StatusInfoType
 }
 
-export type OCPP20StatusNotificationResponse = EmptyObject
-
-export interface OCPP20SetVariablesResponse extends JsonObject {
-  setVariableResult: OCPP20SetVariableResultType[]
+export interface OCPP20HeartbeatResponse extends JsonObject {
+  currentTime: Date
 }
 
 export interface OCPP20InstallCertificateResponse extends JsonObject {
   status: InstallCertificateStatusEnumType
   statusInfo?: StatusInfoType
 }
+
+export interface OCPP20SetVariablesResponse extends JsonObject {
+  setVariableResult: OCPP20SetVariableResultType[]
+}
+
+export type OCPP20StatusNotificationResponse = EmptyObject
index 04a344564bcd202e3a6ab5dcef35798545b09620..e31da2202cc34383f03f437bfe457b2a9b0dc794 100644 (file)
@@ -1,89 +1,86 @@
 import type { JsonObject } from '../../JsonType.js'
 import type { EVSEType, StatusInfoType } from './Common.js'
 
-enum OCPP20ComponentName {
-  AlignedDataCtrlr = 'AlignedDataCtrlr',
-  AuthCacheCtrlr = 'AuthCacheCtrlr',
-  AuthCtrlr = 'AuthCtrlr',
-  CHAdeMOCtrlr = 'CHAdeMOCtrlr',
-  ClockCtrlr = 'ClockCtrlr',
-  CustomizationCtrlr = 'CustomizationCtrlr',
-  DeviceDataCtrlr = 'DeviceDataCtrlr',
-  DisplayMessageCtrlr = 'DisplayMessageCtrlr',
-  ISO15118Ctrlr = 'ISO15118Ctrlr',
-  LocalAuthListCtrlr = 'LocalAuthListCtrlr',
-  MonitoringCtrlr = 'MonitoringCtrlr',
-  OCPPCommCtrlr = 'OCPPCommCtrlr',
-  ReservationCtrlr = 'ReservationCtrlr',
-  SampledDataCtrlr = 'SampledDataCtrlr',
-  SecurityCtrlr = 'SecurityCtrlr',
-  SmartChargingCtrlr = 'SmartChargingCtrlr',
-  TariffCostCtrlr = 'TariffCostCtrlr',
-  TxCtrlr = 'TxCtrlr'
+export enum OCPP20OptionalVariableName {
+  HeartbeatInterval = 'HeartbeatInterval',
+  WebSocketPingInterval = 'WebSocketPingInterval',
 }
 
 export enum OCPP20RequiredVariableName {
-  MessageTimeout = 'MessageTimeout',
+  AuthorizeRemoteStart = 'AuthorizeRemoteStart',
+  BytesPerMessage = 'BytesPerMessage',
+  CertificateEntries = 'CertificateEntries',
+  DateTime = 'DateTime',
+  EVConnectionTimeOut = 'EVConnectionTimeOut',
   FileTransferProtocols = 'FileTransferProtocols',
+  ItemsPerMessage = 'ItemsPerMessage',
+  LocalAuthorizeOffline = 'LocalAuthorizeOffline',
+  LocalPreAuthorize = 'LocalPreAuthorize',
+  MessageAttemptInterval = 'MessageAttemptInterval',
+  MessageAttempts = 'TransactionEvent',
+  MessageTimeout = 'MessageTimeout',
   NetworkConfigurationPriority = 'NetworkConfigurationPriority',
   NetworkProfileConnectionAttempts = 'NetworkProfileConnectionAttempts',
   OfflineThreshold = 'OfflineThreshold',
-  MessageAttempts = 'TransactionEvent',
-  MessageAttemptInterval = 'MessageAttemptInterval',
-  UnlockOnEVSideDisconnect = 'UnlockOnEVSideDisconnect',
-  ResetRetries = 'ResetRetries',
-  ItemsPerMessage = 'ItemsPerMessage',
-  BytesPerMessage = 'BytesPerMessage',
-  DateTime = 'DateTime',
-  TimeSource = 'TimeSource',
   OrganizationName = 'OrganizationName',
-  CertificateEntries = 'CertificateEntries',
+  ResetRetries = 'ResetRetries',
   SecurityProfile = 'SecurityProfile',
-  AuthorizeRemoteStart = 'AuthorizeRemoteStart',
-  LocalAuthorizeOffline = 'LocalAuthorizeOffline',
-  LocalPreAuthorize = 'LocalPreAuthorize',
-  EVConnectionTimeOut = 'EVConnectionTimeOut',
   StopTxOnEVSideDisconnect = 'StopTxOnEVSideDisconnect',
-  TxStartPoint = 'TxStartPoint',
-  TxStopPoint = 'TxStopPoint',
   StopTxOnInvalidId = 'StopTxOnInvalidId',
+  TimeSource = 'TimeSource',
   TxEndedMeasurands = 'TxEndedMeasurands',
   TxStartedMeasurands = 'TxStartedMeasurands',
+  TxStartPoint = 'TxStartPoint',
+  TxStopPoint = 'TxStopPoint',
+  TxUpdatedInterval = 'TxUpdatedInterval',
   TxUpdatedMeasurands = 'TxUpdatedMeasurands',
-  TxUpdatedInterval = 'TxUpdatedInterval'
-}
-
-export enum OCPP20OptionalVariableName {
-  HeartbeatInterval = 'HeartbeatInterval',
-  WebSocketPingInterval = 'WebSocketPingInterval'
+  UnlockOnEVSideDisconnect = 'UnlockOnEVSideDisconnect',
 }
 
 export enum OCPP20VendorVariableName {
-  ConnectionUrl = 'ConnectionUrl'
+  ConnectionUrl = 'ConnectionUrl',
 }
 
 enum AttributeEnumType {
   Actual = 'Actual',
-  Target = 'Target',
+  MaxSet = 'MaxSet',
   MinSet = 'MinSet',
-  MaxSet = 'MaxSet'
+  Target = 'Target',
 }
 
-interface ComponentType extends JsonObject {
-  name: string | OCPP20ComponentName
-  instance?: string
-  evse?: EVSEType
+enum OCPP20ComponentName {
+  AlignedDataCtrlr = 'AlignedDataCtrlr',
+  AuthCacheCtrlr = 'AuthCacheCtrlr',
+  AuthCtrlr = 'AuthCtrlr',
+  CHAdeMOCtrlr = 'CHAdeMOCtrlr',
+  ClockCtrlr = 'ClockCtrlr',
+  CustomizationCtrlr = 'CustomizationCtrlr',
+  DeviceDataCtrlr = 'DeviceDataCtrlr',
+  DisplayMessageCtrlr = 'DisplayMessageCtrlr',
+  ISO15118Ctrlr = 'ISO15118Ctrlr',
+  LocalAuthListCtrlr = 'LocalAuthListCtrlr',
+  MonitoringCtrlr = 'MonitoringCtrlr',
+  OCPPCommCtrlr = 'OCPPCommCtrlr',
+  ReservationCtrlr = 'ReservationCtrlr',
+  SampledDataCtrlr = 'SampledDataCtrlr',
+  SecurityCtrlr = 'SecurityCtrlr',
+  SmartChargingCtrlr = 'SmartChargingCtrlr',
+  TariffCostCtrlr = 'TariffCostCtrlr',
+  TxCtrlr = 'TxCtrlr',
 }
 
-type VariableName =
-  | string
-  | OCPP20RequiredVariableName
-  | OCPP20OptionalVariableName
-  | OCPP20VendorVariableName
+enum SetVariableStatusEnumType {
+  Accepted = 'Accepted',
+  NotSupportedAttributeType = 'NotSupportedAttributeType',
+  RebootRequired = 'RebootRequired',
+  Rejected = 'Rejected',
+  UnknownComponent = 'UnknownComponent',
+  UnknownVariable = 'UnknownVariable',
+}
 
-interface VariableType extends JsonObject {
-  name: VariableName
-  instance?: string
+export interface OCPP20ComponentVariableType extends JsonObject {
+  component: ComponentType
+  variable?: VariableType
 }
 
 export interface OCPP20SetVariableDataType extends JsonObject {
@@ -93,24 +90,27 @@ export interface OCPP20SetVariableDataType extends JsonObject {
   variable: VariableType
 }
 
-enum SetVariableStatusEnumType {
-  Accepted = 'Accepted',
-  Rejected = 'Rejected',
-  UnknownComponent = 'UnknownComponent',
-  UnknownVariable = 'UnknownVariable',
-  NotSupportedAttributeType = 'NotSupportedAttributeType',
-  RebootRequired = 'RebootRequired'
-}
-
 export interface OCPP20SetVariableResultType extends JsonObject {
-  attributeType?: AttributeEnumType
   attributeStatus: SetVariableStatusEnumType
+  attributeStatusInfo?: StatusInfoType
+  attributeType?: AttributeEnumType
   component: ComponentType
   variable: VariableType
-  attributeStatusInfo?: StatusInfoType
 }
 
-export interface OCPP20ComponentVariableType extends JsonObject {
-  component: ComponentType
-  variable?: VariableType
+interface ComponentType extends JsonObject {
+  evse?: EVSEType
+  instance?: string
+  name: OCPP20ComponentName | string
+}
+
+type VariableName =
+  | OCPP20OptionalVariableName
+  | OCPP20RequiredVariableName
+  | OCPP20VendorVariableName
+  | string
+
+interface VariableType extends JsonObject {
+  instance?: string
+  name: VariableName
 }
index 9c29390e6974923bdaa37a508696bcad5a906d28..cd20da95dc4080e1433003cc07ce3306f10ade96 100644 (file)
@@ -1,7 +1,7 @@
 import { OCPP16ChargePointErrorCode } from './1.6/ChargePointErrorCode.js'
 
 export const ChargePointErrorCode = {
-  ...OCPP16ChargePointErrorCode
+  ...OCPP16ChargePointErrorCode,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ChargePointErrorCode = OCPP16ChargePointErrorCode
index efc25b90083ad54a28aa977ee587e63851d643f0..9009b76fce9da759b766a05ac5dbc37ab648b143 100644 (file)
@@ -1,29 +1,36 @@
 import {
   type OCPP16ChargingProfile,
   OCPP16ChargingProfileKindType,
+  OCPP16ChargingProfilePurposeType,
   OCPP16ChargingRateUnitType,
   type OCPP16ChargingSchedulePeriod,
-  OCPP16RecurrencyKindType
+  OCPP16RecurrencyKindType,
 } from './1.6/ChargingProfile.js'
 
 export type ChargingProfile = OCPP16ChargingProfile
 
 export type ChargingSchedulePeriod = OCPP16ChargingSchedulePeriod
 
+export const ChargingProfilePurposeType = {
+  ...OCPP16ChargingProfilePurposeType,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type ChargingProfilePurposeType = OCPP16ChargingProfilePurposeType
+
 export const ChargingProfileKindType = {
-  ...OCPP16ChargingProfileKindType
+  ...OCPP16ChargingProfileKindType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ChargingProfileKindType = OCPP16ChargingProfileKindType
 
 export const RecurrencyKindType = {
-  ...OCPP16RecurrencyKindType
+  ...OCPP16RecurrencyKindType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type RecurrencyKindType = OCPP16RecurrencyKindType
 
 export const ChargingRateUnitType = {
-  ...OCPP16ChargingRateUnitType
+  ...OCPP16ChargingRateUnitType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ChargingRateUnitType = OCPP16ChargingRateUnitType
index 55b92f106ed3d704200d76ca32144eadab4c3477..7190d9a4483f3ccb53095b5c76eb2d645651d68e 100644 (file)
@@ -2,15 +2,15 @@ import type { JsonObject } from '../JsonType.js'
 
 export enum GenericStatus {
   Accepted = 'Accepted',
-  Rejected = 'Rejected'
-}
-
-export interface GenericResponse extends JsonObject {
-  status: GenericStatus
+  Rejected = 'Rejected',
 }
 
 export enum RegistrationStatusEnumType {
   ACCEPTED = 'Accepted',
   PENDING = 'Pending',
-  REJECTED = 'Rejected'
+  REJECTED = 'Rejected',
+}
+
+export interface GenericResponse extends JsonObject {
+  status: GenericStatus
 }
index f1cd34ba3f8ec0cd79b70de1d8378fb409d7e511..f076074238b8c6aabbce1efa9127a8ce5f334e69 100644 (file)
@@ -1,51 +1,52 @@
 import type { JsonObject } from '../JsonType.js'
+
 import {
   OCPP16StandardParametersKey,
   OCPP16SupportedFeatureProfiles,
-  OCPP16VendorParametersKey
+  OCPP16VendorParametersKey,
 } from './1.6/Configuration.js'
 import {
   OCPP20OptionalVariableName,
   OCPP20RequiredVariableName,
-  OCPP20VendorVariableName
+  OCPP20VendorVariableName,
 } from './2.0/Variables.js'
 
+export enum ConnectorPhaseRotation {
+  NotApplicable = 'NotApplicable',
+  RST = 'RST',
+  RTS = 'RTS',
+  SRT = 'SRT',
+  STR = 'STR',
+  TRS = 'TRS',
+  TSR = 'TSR',
+  Unknown = 'Unknown',
+}
+
+export type ConfigurationKeyType = StandardParametersKey | string | VendorParametersKey
+
+export interface OCPPConfigurationKey extends JsonObject {
+  key: ConfigurationKeyType
+  readonly: boolean
+  value?: string
+}
+
 export const StandardParametersKey = {
   ...OCPP16StandardParametersKey,
   ...OCPP20RequiredVariableName,
-  ...OCPP20OptionalVariableName
+  ...OCPP20OptionalVariableName,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type StandardParametersKey = OCPP16StandardParametersKey
 
 export const VendorParametersKey = {
   ...OCPP16VendorParametersKey,
-  ...OCPP20VendorVariableName
+  ...OCPP20VendorVariableName,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type VendorParametersKey = OCPP16VendorParametersKey
 
 export const SupportedFeatureProfiles = {
-  ...OCPP16SupportedFeatureProfiles
+  ...OCPP16SupportedFeatureProfiles,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type SupportedFeatureProfiles = OCPP16SupportedFeatureProfiles
-
-export enum ConnectorPhaseRotation {
-  NotApplicable = 'NotApplicable',
-  Unknown = 'Unknown',
-  RST = 'RST',
-  RTS = 'RTS',
-  SRT = 'SRT',
-  STR = 'STR',
-  TRS = 'TRS',
-  TSR = 'TSR'
-}
-
-export type ConfigurationKeyType = string | StandardParametersKey | VendorParametersKey
-
-export interface OCPPConfigurationKey extends JsonObject {
-  key: ConfigurationKeyType
-  readonly: boolean
-  value?: string
-}
index 9c4d246994bd09ecec97242fb247a6366ea46048..ee31829194f7e9d52579e3fe40f9761d0d622b0b 100644 (file)
@@ -3,7 +3,7 @@ import { OCPP20ConnectorStatusEnumType } from './2.0/Common.js'
 
 export const ConnectorStatusEnum = {
   ...OCPP16ChargePointStatus,
-  ...OCPP20ConnectorStatusEnumType
+  ...OCPP20ConnectorStatusEnumType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ConnectorStatusEnum = OCPP16ChargePointStatus | OCPP20ConnectorStatusEnumType
index d6d8357fc71cef712facbd4b305f913aa08937ac..40a6e7353ac25979ca10bff45a06d1d8cb2b2440 100644 (file)
@@ -1,23 +1,25 @@
 export enum ErrorType {
+  // Payload for Action is syntactically incorrect or not conform the PDU structure for Action
+  FORMAT_VIOLATION = 'FormatViolation',
+  /** @deprecated use FORMAT_VIOLATION instead */
+  FORMATION_VIOLATION = 'FormationViolation',
+  // Any other error not covered by the other ones
+  GENERIC_ERROR = 'GenericError',
+  // An internal error occurred and the receiver was not able to process the requested Action successfully
+  INTERNAL_ERROR = 'InternalError',
   // Requested Action is not known by receiver
   NOT_IMPLEMENTED = 'NotImplemented',
   // Requested Action is recognized but not supported by the receiver
   NOT_SUPPORTED = 'NotSupported',
-  // An internal error occurred and the receiver was not able to process the requested Action successfully
-  INTERNAL_ERROR = 'InternalError',
+  // Payload for Action is syntactically correct but at least one of the fields violates occurrence constraints
+  OCCURRENCE_CONSTRAINT_VIOLATION = 'OccurrenceConstraintViolation',
+  // Payload is syntactically correct but at least one field contains an invalid value
+  PROPERTY_CONSTRAINT_VIOLATION = 'PropertyConstraintViolation',
   // Payload for Action is incomplete
   PROTOCOL_ERROR = 'ProtocolError',
   // During the processing of Action a security issue occurred preventing receiver from completing the Action successfully
   SECURITY_ERROR = 'SecurityError',
-  // Payload for Action is syntactically incorrect or not conform the PDU structure for Action
-  FORMATION_VIOLATION = 'FormationViolation',
-  FORMAT_VIOLATION = 'FormatViolation',
-  // Payload is syntactically correct but at least one field contains an invalid value
-  PROPERTY_CONSTRAINT_VIOLATION = 'PropertyConstraintViolation',
-  // Payload for Action is syntactically correct but at least one of the fields violates occurrence constraints
-  OCCURRENCE_CONSTRAINT_VIOLATION = 'OccurrenceConstraintViolation',
+  // eslint-disable-next-line @cspell/spellchecker
   // Payload for Action is syntactically correct but at least one of the fields violates data type constraints (e.g. "somestring" = 12)
   TYPE_CONSTRAINT_VIOLATION = 'TypeConstraintViolation',
-  // Any other error not covered by the previous ones
-  GENERIC_ERROR = 'GenericError'
 }
index 972ed474cc15fbdabc6b9a5049f5f44a8738091e..d8573eee5fe3ce5146c5c127d7f5d599eeb082b2 100644 (file)
@@ -1,5 +1,7 @@
+/* eslint-disable perfectionist/sort-enums */
 export enum MessageType {
   CALL_MESSAGE = 2, // Caller to Callee
   CALL_RESULT_MESSAGE = 3, // Callee to Caller
-  CALL_ERROR_MESSAGE = 4 // Callee to Caller
+  CALL_ERROR_MESSAGE = 4, // Callee to Caller
 }
+/* eslint-enable perfectionist/sort-enums */
index aec3d19e1934e4e61a030713d9018e58ad05f158..7c222e3a719e81f5394635dfb4e3fbfa103e8a67 100644 (file)
@@ -5,39 +5,39 @@ import {
   OCPP16MeterValueMeasurand,
   OCPP16MeterValuePhase,
   OCPP16MeterValueUnit,
-  type OCPP16SampledValue
+  type OCPP16SampledValue,
 } from './1.6/MeterValues.js'
 
+export type MeterValue = OCPP16MeterValue
+
 export const MeterValueUnit = {
-  ...OCPP16MeterValueUnit
+  ...OCPP16MeterValueUnit,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MeterValueUnit = OCPP16MeterValueUnit
 
 export const MeterValueContext = {
-  ...OCPP16MeterValueContext
+  ...OCPP16MeterValueContext,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MeterValueContext = OCPP16MeterValueContext
 
-export const MeterValueMeasurand = {
-  ...OCPP16MeterValueMeasurand
+export const MeterValueLocation = {
+  ...OCPP16MeterValueLocation,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
-export type MeterValueMeasurand = OCPP16MeterValueMeasurand
+export type MeterValueLocation = OCPP16MeterValueLocation
 
-export const MeterValueLocation = {
-  ...OCPP16MeterValueLocation
+export const MeterValueMeasurand = {
+  ...OCPP16MeterValueMeasurand,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
-export type MeterValueLocation = OCPP16MeterValueLocation
+export type MeterValueMeasurand = OCPP16MeterValueMeasurand
 
 export const MeterValuePhase = {
-  ...OCPP16MeterValuePhase
+  ...OCPP16MeterValuePhase,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MeterValuePhase = OCPP16MeterValuePhase
 
 export type SampledValue = OCPP16SampledValue
-
-export type MeterValue = OCPP16MeterValue
index 3342d81d7f79419b79e3c46b0222a838412a82ed..1df59397e5155035609cd64e76b913494f3e8085 100644 (file)
@@ -1,3 +1,3 @@
 export enum OCPPProtocol {
-  JSON = 'json'
+  JSON = 'json',
 }
index 37690b5ef70d561ea59e352bfdfb97ebb40e94e7..a19fc28033e6afe9f9475cbd8d12334185ad3b04 100644 (file)
@@ -1,5 +1,5 @@
 export enum OCPPVersion {
   VERSION_16 = '1.6',
   VERSION_20 = '2.0',
-  VERSION_201 = '2.0.1'
+  VERSION_201 = '2.0.1',
 }
index 12e1c68c3e6996cf9c3d1ddb48158662ea7ab0df..79bd00b39427b93f8a005b1799699b51bc05a407 100644 (file)
@@ -1,8 +1,10 @@
 import type { ChargingStation } from '../../charging-station/index.js'
 import type { OCPPError } from '../../exception/index.js'
 import type { JsonType } from '../JsonType.js'
-import { OCPP16DiagnosticsStatus } from './1.6/DiagnosticsStatus.js'
 import type { OCPP16MeterValuesRequest } from './1.6/MeterValues.js'
+import type { MessageType } from './MessageType.js'
+
+import { OCPP16DiagnosticsStatus } from './1.6/DiagnosticsStatus.js'
 import {
   OCPP16AvailabilityType,
   type OCPP16BootNotificationRequest,
@@ -16,100 +18,99 @@ import {
   OCPP16MessageTrigger,
   OCPP16RequestCommand,
   type OCPP16ReserveNowRequest,
-  type OCPP16StatusNotificationRequest
+  type OCPP16StatusNotificationRequest,
 } from './1.6/Requests.js'
 import { OperationalStatusEnumType } from './2.0/Common.js'
 import {
   type OCPP20BootNotificationRequest,
   OCPP20IncomingRequestCommand,
   OCPP20RequestCommand,
-  type OCPP20StatusNotificationRequest
+  type OCPP20StatusNotificationRequest,
 } from './2.0/Requests.js'
-import type { MessageType } from './MessageType.js'
 
-export const RequestCommand = {
-  ...OCPP16RequestCommand,
-  ...OCPP20RequestCommand
-} as const
-// eslint-disable-next-line @typescript-eslint/no-redeclare
-export type RequestCommand = OCPP16RequestCommand | OCPP20RequestCommand
+export type BootNotificationRequest = OCPP16BootNotificationRequest | OCPP20BootNotificationRequest
 
-export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType]
+export type CachedRequest = [
+  ResponseCallback,
+  ErrorCallback,
+  IncomingRequestCommand | RequestCommand,
+  JsonType
+]
 
-export interface RequestParams {
-  skipBufferingOnError?: boolean
-  triggerMessage?: boolean
-  throwError?: boolean
-}
+export type DataTransferRequest = OCPP16DataTransferRequest
+
+export type DiagnosticsStatusNotificationRequest = OCPP16DiagnosticsStatusNotificationRequest
+
+export type ErrorCallback = (ocppError: OCPPError, requestStatistic?: boolean) => void
+
+export type FirmwareStatusNotificationRequest = OCPP16FirmwareStatusNotificationRequest
+
+export type HeartbeatRequest = OCPP16HeartbeatRequest
+
+export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType]
+
+export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType]
 
 export const IncomingRequestCommand = {
   ...OCPP16IncomingRequestCommand,
-  ...OCPP20IncomingRequestCommand
+  ...OCPP20IncomingRequestCommand,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type IncomingRequestCommand = OCPP16IncomingRequestCommand | OCPP20IncomingRequestCommand
 
-export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType]
-
 export type IncomingRequestHandler = (
   chargingStation: ChargingStation,
   commandPayload: JsonType
 ) => JsonType | Promise<JsonType>
 
-export type ResponseCallback = (payload: JsonType, requestPayload: JsonType) => void
-
-export type ErrorCallback = (ocppError: OCPPError, requestStatistic?: boolean) => void
+export const RequestCommand = {
+  ...OCPP16RequestCommand,
+  ...OCPP20RequestCommand,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type RequestCommand = OCPP16RequestCommand | OCPP20RequestCommand
 
-export type CachedRequest = [
-  ResponseCallback,
-  ErrorCallback,
-  RequestCommand | IncomingRequestCommand,
-  JsonType
-]
+export interface RequestParams {
+  skipBufferingOnError?: boolean
+  throwError?: boolean
+  triggerMessage?: boolean
+}
 
 export const MessageTrigger = {
-  ...OCPP16MessageTrigger
+  ...OCPP16MessageTrigger,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MessageTrigger = OCPP16MessageTrigger
 
-export type BootNotificationRequest = OCPP16BootNotificationRequest | OCPP20BootNotificationRequest
+export type MeterValuesRequest = OCPP16MeterValuesRequest
 
-export type HeartbeatRequest = OCPP16HeartbeatRequest
+export type ResponseCallback = (payload: JsonType, requestPayload: JsonType) => void
 
 export type StatusNotificationRequest =
   | OCPP16StatusNotificationRequest
   | OCPP20StatusNotificationRequest
 
-export type MeterValuesRequest = OCPP16MeterValuesRequest
-
-export type DataTransferRequest = OCPP16DataTransferRequest
-
-export type DiagnosticsStatusNotificationRequest = OCPP16DiagnosticsStatusNotificationRequest
-
-export type FirmwareStatusNotificationRequest = OCPP16FirmwareStatusNotificationRequest
-
 export const AvailabilityType = {
   ...OCPP16AvailabilityType,
-  ...OperationalStatusEnumType
+  ...OperationalStatusEnumType,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type AvailabilityType = OCPP16AvailabilityType | OperationalStatusEnumType
 
+export type CancelReservationRequest = OCPP16CancelReservationRequest
+
 export const DiagnosticsStatus = {
-  ...OCPP16DiagnosticsStatus
+  ...OCPP16DiagnosticsStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type DiagnosticsStatus = OCPP16DiagnosticsStatus
 
 export const FirmwareStatus = {
-  ...OCPP16FirmwareStatus
+  ...OCPP16FirmwareStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type FirmwareStatus = OCPP16FirmwareStatus
 
-export type ResponseType = JsonType | OCPPError
-
 export type ReserveNowRequest = OCPP16ReserveNowRequest
 
-export type CancelReservationRequest = OCPP16CancelReservationRequest
+export type ResponseType = JsonType | OCPPError
index 5f15838a5d303639bbc1ebff347bd500809eb67b..bc4fa26662950aecd467a22e84b6c9b4d559c460 100644 (file)
@@ -1,13 +1,13 @@
 import type { OCPP16ReserveNowRequest } from './1.6/Requests.js'
 
-export type Reservation = OCPP16ReserveNowRequest
-
-export type ReservationKey = keyof Reservation
-
 export enum ReservationTerminationReason {
-  EXPIRED = 'Expired',
-  TRANSACTION_STARTED = 'TransactionStarted',
   CONNECTOR_STATE_CHANGED = 'ConnectorStateChanged',
+  EXPIRED = 'Expired',
+  REPLACE_EXISTING = 'ReplaceExisting',
   RESERVATION_CANCELED = 'ReservationCanceled',
-  REPLACE_EXISTING = 'ReplaceExisting'
+  TRANSACTION_STARTED = 'TransactionStarted',
 }
+
+export type Reservation = OCPP16ReserveNowRequest
+
+export type ReservationKey = keyof Reservation
index aac5a5fc3774b39d008b454d5e85faad021e2016..e6b88f00dfe807420e92b6a0d625cf500a7c63f8 100644 (file)
@@ -1,6 +1,10 @@
 import type { ChargingStation } from '../../charging-station/index.js'
 import type { JsonType } from '../JsonType.js'
 import type { OCPP16MeterValuesResponse } from './1.6/MeterValues.js'
+import type { OCPP20BootNotificationResponse, OCPP20ClearCacheResponse } from './2.0/Responses.js'
+import type { ErrorType } from './ErrorType.js'
+import type { MessageType } from './MessageType.js'
+
 import {
   OCPP16AvailabilityStatus,
   type OCPP16BootNotificationResponse,
@@ -15,93 +19,90 @@ import {
   OCPP16ReservationStatus,
   type OCPP16StatusNotificationResponse,
   OCPP16TriggerMessageStatus,
-  OCPP16UnlockStatus
+  OCPP16UnlockStatus,
 } from './1.6/Responses.js'
-import type { OCPP20BootNotificationResponse, OCPP20ClearCacheResponse } from './2.0/Responses.js'
 import { type GenericResponse, GenericStatus } from './Common.js'
-import type { ErrorType } from './ErrorType.js'
-import type { MessageType } from './MessageType.js'
-
-export type Response = [MessageType.CALL_RESULT_MESSAGE, string, JsonType]
-
-export type ErrorResponse = [MessageType.CALL_ERROR_MESSAGE, string, ErrorType, string, JsonType]
-
-export type ResponseHandler = (
-  chargingStation: ChargingStation,
-  payload: JsonType,
-  requestPayload?: JsonType
-) => void | Promise<void>
 
 export type BootNotificationResponse =
   | OCPP16BootNotificationResponse
   | OCPP20BootNotificationResponse
 
-export type HeartbeatResponse = OCPP16HeartbeatResponse
+export type CancelReservationResponse = GenericResponse
 
 export type ClearCacheResponse = GenericResponse | OCPP20ClearCacheResponse
 
-export type StatusNotificationResponse = OCPP16StatusNotificationResponse
-
-export type MeterValuesResponse = OCPP16MeterValuesResponse
-
 export type DataTransferResponse = OCPP16DataTransferResponse
 
 export type DiagnosticsStatusNotificationResponse = OCPP16DiagnosticsStatusNotificationResponse
 
+export type ErrorResponse = [MessageType.CALL_ERROR_MESSAGE, string, ErrorType, string, JsonType]
+
 export type FirmwareStatusNotificationResponse = OCPP16FirmwareStatusNotificationResponse
 
+export type HeartbeatResponse = OCPP16HeartbeatResponse
+
+export type MeterValuesResponse = OCPP16MeterValuesResponse
+
+export type Response = [MessageType.CALL_RESULT_MESSAGE, string, JsonType]
+
+export type ResponseHandler = (
+  chargingStation: ChargingStation,
+  payload: JsonType,
+  requestPayload?: JsonType
+) => Promise<void> | void
+
+export type StatusNotificationResponse = OCPP16StatusNotificationResponse
+
 export const AvailabilityStatus = {
-  ...OCPP16AvailabilityStatus
+  ...OCPP16AvailabilityStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type AvailabilityStatus = OCPP16AvailabilityStatus
 
 export const ChargingProfileStatus = {
-  ...OCPP16ChargingProfileStatus
+  ...OCPP16ChargingProfileStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ChargingProfileStatus = OCPP16ChargingProfileStatus
 
 export const ClearChargingProfileStatus = {
-  ...OCPP16ClearChargingProfileStatus
+  ...OCPP16ClearChargingProfileStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ClearChargingProfileStatus = OCPP16ClearChargingProfileStatus
 
 export const ConfigurationStatus = {
-  ...OCPP16ConfigurationStatus
+  ...OCPP16ConfigurationStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type ConfigurationStatus = OCPP16ConfigurationStatus
 
 export const UnlockStatus = {
-  ...OCPP16UnlockStatus
+  ...OCPP16UnlockStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type UnlockStatus = OCPP16UnlockStatus
 
 export const TriggerMessageStatus = {
-  ...OCPP16TriggerMessageStatus
+  ...OCPP16TriggerMessageStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type TriggerMessageStatus = OCPP16TriggerMessageStatus
 
 export const DataTransferStatus = {
-  ...OCPP16DataTransferStatus
+  ...OCPP16DataTransferStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type DataTransferStatus = OCPP16DataTransferStatus
 
-export type ReservationStatus = OCPP16ReservationStatus
-// eslint-disable-next-line @typescript-eslint/no-redeclare
 export const ReservationStatus = {
-  ...OCPP16ReservationStatus
+  ...OCPP16ReservationStatus,
 } as const
-
-export type CancelReservationStatus = GenericStatus
 // eslint-disable-next-line @typescript-eslint/no-redeclare
+export type ReservationStatus = OCPP16ReservationStatus
+
 export const CancelReservationStatus = {
-  ...GenericStatus
+  ...GenericStatus,
 } as const
-
-export type CancelReservationResponse = GenericResponse
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type CancelReservationStatus = GenericStatus
index 75034efc92a6550afedc481b0ef3901116134b6c..83f16acc4fcc14d086495402e48c3e2cd1b90040 100644 (file)
@@ -6,11 +6,11 @@ import {
   type OCPP16StartTransactionResponse,
   OCPP16StopTransactionReason,
   type OCPP16StopTransactionRequest,
-  type OCPP16StopTransactionResponse
+  type OCPP16StopTransactionResponse,
 } from './1.6/Transaction.js'
 
 export const AuthorizationStatus = {
-  ...OCPP16AuthorizationStatus
+  ...OCPP16AuthorizationStatus,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type AuthorizationStatus = OCPP16AuthorizationStatus
@@ -19,16 +19,16 @@ export type AuthorizeRequest = OCPP16AuthorizeRequest
 
 export type AuthorizeResponse = OCPP16AuthorizeResponse
 
+export type StartTransactionRequest = OCPP16StartTransactionRequest
+
+export type StartTransactionResponse = OCPP16StartTransactionResponse
+
 export const StopTransactionReason = {
-  ...OCPP16StopTransactionReason
+  ...OCPP16StopTransactionReason,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type StopTransactionReason = OCPP16StopTransactionReason
 
-export type StartTransactionRequest = OCPP16StartTransactionRequest
-
-export type StartTransactionResponse = OCPP16StartTransactionResponse
-
 export type StopTransactionRequest = OCPP16StopTransactionRequest
 
 export type StopTransactionResponse = OCPP16StopTransactionResponse
index bdd88abaeb6cd04e4ab8fc79752343ade355d295..8f9b9204c32eb0ada6afe4f9df74f36b9c2cca10 100644 (file)
@@ -1,42 +1,42 @@
 import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
 
 interface StatisticsData {
-  name: string
-  requestCount: number
-  responseCount: number
+  avgTimeMeasurement: number
+  currentTimeMeasurement: number
   errorCount: number
-  timeMeasurementCount: number
-  measurementTimeSeries: Array<{
+  maxTimeMeasurement: number
+  measurementTimeSeries: {
     timestamp: number
     value: number
-  }>
-  currentTimeMeasurement: number
-  minTimeMeasurement: number
-  maxTimeMeasurement: number
-  totalTimeMeasurement: number
-  avgTimeMeasurement: number
+  }[]
   medTimeMeasurement: number
+  minTimeMeasurement: number
+  name: string
   ninetyFiveThPercentileTimeMeasurement: number
-  stdDevTimeMeasurement: number
+  requestCount: number
+  responseCount: number
+  stdTimeMeasurement: number
+  timeMeasurementCount: number
+  totalTimeMeasurement: number
 }
 
 @Entity()
 export class PerformanceRecord {
-  @PrimaryKey()
-    id!: string
-
   @Property()
-    name!: string
+  createdAt!: Date
+
+  @PrimaryKey()
+  id!: string
 
   @Property()
-    uri!: string
+  name!: string
 
   @Property()
-    createdAt!: Date
+  statisticsData!: Partial<StatisticsData>[]
 
   @Property()
-    updatedAt?: Date
+  updatedAt?: Date
 
   @Property()
-    statisticsData!: Array<Partial<StatisticsData>>
+  uri!: string
 }
index f6b793a60748beaa8454fdb80e13510553a25898..49565046dda460d8fa12d87db67b451216c02969 100644 (file)
@@ -2,14 +2,14 @@
 
 import { Queue } from 'mnemonist'
 
-import { Constants } from './Constants.js'
+import { isAsyncFunction } from './Utils.js'
 
 export enum AsyncLockType {
   configuration = 'configuration',
-  performance = 'performance'
+  performance = 'performance',
 }
 
-type ResolveType = (value: void | PromiseLike<void>) => void
+type ResolveType = (value: PromiseLike<void> | void) => void
 
 export class AsyncLock {
   private static readonly asyncLocks = new Map<AsyncLockType, AsyncLock>()
@@ -21,12 +21,17 @@ export class AsyncLock {
     this.resolveQueue = new Queue<ResolveType>()
   }
 
-  public static async runExclusive<T>(type: AsyncLockType, fn: () => T | Promise<T>): Promise<T> {
-    return await AsyncLock.acquire(type)
-      .then(fn)
-      .finally(() => {
-        AsyncLock.release(type).catch(Constants.EMPTY_FUNCTION)
-      })
+  public static async runExclusive<T>(type: AsyncLockType, fn: () => Promise<T> | T): Promise<T> {
+    try {
+      await AsyncLock.acquire(type)
+      if (isAsyncFunction(fn)) {
+        return await fn()
+      } else {
+        return fn() as T
+      }
+    } finally {
+      await AsyncLock.release(type)
+    }
   }
 
   private static async acquire (type: AsyncLockType): Promise<void> {
@@ -40,25 +45,24 @@ export class AsyncLock {
     })
   }
 
+  private static getAsyncLock (type: AsyncLockType): AsyncLock {
+    if (!AsyncLock.asyncLocks.has(type)) {
+      AsyncLock.asyncLocks.set(type, new AsyncLock())
+    }
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return AsyncLock.asyncLocks.get(type)!
+  }
+
   private static async release (type: AsyncLockType): Promise<void> {
     const asyncLock = AsyncLock.getAsyncLock(type)
     if (asyncLock.resolveQueue.size === 0 && asyncLock.acquired) {
       asyncLock.acquired = false
       return
     }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const queuedResolve = asyncLock.resolveQueue.dequeue()!
     await new Promise<void>(resolve => {
-      queuedResolve()
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      asyncLock.resolveQueue.dequeue()!()
       resolve()
     })
   }
-
-  private static getAsyncLock (type: AsyncLockType): AsyncLock {
-    if (!AsyncLock.asyncLocks.has(type)) {
-      AsyncLock.asyncLocks.set(type, new AsyncLock())
-    }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return AsyncLock.asyncLocks.get(type)!
-  }
 }
index 7b5346d52392deb1996673bc7210e1a3bf300708..205fcfacc1403cb7d25f9ef905f3b67e384f643b 100644 (file)
@@ -3,7 +3,7 @@ import type {
   ChargingStationAutomaticTransactionGeneratorConfiguration,
   ConnectorStatus,
   EvseStatusConfiguration,
-  EvseStatusWorkerType
+  EvseStatusWorkerType,
 } from '../types/index.js'
 
 export const buildChargingStationAutomaticTransactionGeneratorConfiguration = (
@@ -13,9 +13,9 @@ export const buildChargingStationAutomaticTransactionGeneratorConfiguration = (
     automaticTransactionGenerator: chargingStation.getAutomaticTransactionGeneratorConfiguration(),
     ...(chargingStation.automaticTransactionGenerator?.connectorsStatus != null && {
       automaticTransactionGeneratorStatuses: [
-        ...chargingStation.automaticTransactionGenerator.connectorsStatus.values()
-      ]
-    })
+        ...chargingStation.automaticTransactionGenerator.connectorsStatus.values(),
+      ],
+    }),
   }
 }
 
@@ -25,15 +25,15 @@ export const buildConnectorsStatus = (chargingStation: ChargingStation): Connect
   )
 }
 
-export const enum OutputFormat {
+export enum OutputFormat {
   configuration = 'configuration',
-  worker = 'worker'
+  worker = 'worker',
 }
 
 export const buildEvsesStatus = (
   chargingStation: ChargingStation,
   outputFormat: OutputFormat = OutputFormat.configuration
-): Array<EvseStatusWorkerType | EvseStatusConfiguration> => {
+): (EvseStatusConfiguration | EvseStatusWorkerType)[] => {
   // eslint-disable-next-line array-callback-return
   return [...chargingStation.evses.values()].map(evseStatus => {
     const connectorsStatus = [...evseStatus.connectors.values()].map(
@@ -41,18 +41,18 @@ export const buildEvsesStatus = (
     )
     let status: EvseStatusConfiguration
     switch (outputFormat) {
-      case OutputFormat.worker:
-        return {
-          ...evseStatus,
-          connectors: connectorsStatus
-        }
       case OutputFormat.configuration:
         status = {
           ...evseStatus,
-          connectorsStatus
+          connectorsStatus,
         }
         delete (status as EvseStatusWorkerType).connectors
         return status
+      case OutputFormat.worker:
+        return {
+          ...evseStatus,
+          connectors: connectorsStatus,
+        }
     }
   })
 }
index 25a1475bc2a5f6ec9a6ea40df049048db9538ad8..dda5f1aac2b5fa3b3b27df1d2f5ba50a122f35e0 100644 (file)
@@ -1,11 +1,9 @@
-import { type FSWatcher, readFileSync, watch } from 'node:fs'
+import chalk from 'chalk'
+import { existsSync, type FSWatcher, readFileSync, watch } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { env } from 'node:process'
 import { fileURLToPath } from 'node:url'
 
-import chalk from 'chalk'
-import { mergeDeepRight, once } from 'rambda'
-
 import {
   ApplicationProtocol,
   ApplicationProtocolVersion,
@@ -18,14 +16,14 @@ import {
   StorageType,
   SupervisionUrlDistribution,
   type UIServerConfiguration,
-  type WorkerConfiguration
+  type WorkerConfiguration,
 } from '../types/index.js'
 import {
   DEFAULT_ELEMENT_ADD_DELAY,
   DEFAULT_POOL_MAX_SIZE,
   DEFAULT_POOL_MIN_SIZE,
   DEFAULT_WORKER_START_DELAY,
-  WorkerProcessType
+  WorkerProcessType,
 } from '../worker/index.js'
 import {
   buildPerformanceUriFilePath,
@@ -33,44 +31,125 @@ import {
   checkWorkerProcessType,
   getDefaultPerformanceStorageUri,
   handleFileException,
-  logPrefix
+  logPrefix,
 } from './ConfigurationUtils.js'
 import { Constants } from './Constants.js'
-import { hasOwnProp, isCFEnvironment } from './Utils.js'
+import { has, isCFEnvironment, mergeDeepRight, once } from './Utils.js'
 
 type ConfigurationSectionType =
   | LogConfiguration
   | StorageConfiguration
-  | WorkerConfiguration
   | UIServerConfiguration
+  | WorkerConfiguration
+
+const defaultUIServerConfiguration: UIServerConfiguration = {
+  enabled: false,
+  options: {
+    host: Constants.DEFAULT_UI_SERVER_HOST,
+    port: Constants.DEFAULT_UI_SERVER_PORT,
+  },
+  type: ApplicationProtocol.WS,
+  version: ApplicationProtocolVersion.VERSION_11,
+}
+
+const defaultStorageConfiguration: StorageConfiguration = {
+  enabled: true,
+  type: StorageType.NONE,
+}
+
+const defaultLogConfiguration: LogConfiguration = {
+  enabled: true,
+  errorFile: 'logs/error.log',
+  file: 'logs/combined.log',
+  format: 'simple',
+  level: 'info',
+  rotate: true,
+  statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
+}
+
+const defaultWorkerConfiguration: WorkerConfiguration = {
+  elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
+  elementsPerWorker: 'auto',
+  poolMaxSize: DEFAULT_POOL_MAX_SIZE,
+  poolMinSize: DEFAULT_POOL_MIN_SIZE,
+  processType: WorkerProcessType.workerSet,
+  startDelay: DEFAULT_WORKER_START_DELAY,
+}
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class Configuration {
   public static configurationChangeCallback?: () => Promise<void>
 
-  private static readonly configurationFile = join(
-    dirname(fileURLToPath(import.meta.url)),
-    'assets',
-    'config.json'
-  )
-
-  private static configurationFileReloading = false
   private static configurationData?: ConfigurationData
+  private static configurationFile: string | undefined
+  private static configurationFileReloading = false
   private static configurationFileWatcher?: FSWatcher
-  private static readonly configurationSectionCache = new Map<
-  ConfigurationSection,
-  ConfigurationSectionType
-  >([
-    [ConfigurationSection.log, Configuration.buildLogSection()],
-    [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
-    [ConfigurationSection.worker, Configuration.buildWorkerSection()],
-    [ConfigurationSection.uiServer, Configuration.buildUIServerSection()]
-  ])
+  private static configurationSectionCache: Map<ConfigurationSection, ConfigurationSectionType>
+
+  static {
+    const configurationFile = join(dirname(fileURLToPath(import.meta.url)), 'assets', 'config.json')
+    if (existsSync(configurationFile)) {
+      Configuration.configurationFile = configurationFile
+    } else {
+      console.error(
+        `${chalk.green(logPrefix())} ${chalk.red(
+          "Configuration file './src/assets/config.json' not found, using default configuration"
+        )}`
+      )
+      Configuration.configurationData = {
+        log: defaultLogConfiguration,
+        performanceStorage: defaultStorageConfiguration,
+        stationTemplateUrls: [
+          {
+            file: 'siemens.station-template.json',
+            numberOfStations: 1,
+          },
+        ],
+        supervisionUrlDistribution: SupervisionUrlDistribution.ROUND_ROBIN,
+        supervisionUrls: 'ws://localhost:8180/steve/websocket/CentralSystemService',
+        uiServer: defaultUIServerConfiguration,
+        worker: defaultWorkerConfiguration,
+      }
+    }
+    Configuration.configurationSectionCache = new Map<
+      ConfigurationSection,
+      ConfigurationSectionType
+    >([
+      [ConfigurationSection.log, Configuration.buildLogSection()],
+      [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
+      [ConfigurationSection.uiServer, Configuration.buildUIServerSection()],
+      [ConfigurationSection.worker, Configuration.buildWorkerSection()],
+    ])
+  }
 
   private constructor () {
     // This is intentional
   }
 
+  public static getConfigurationData (): ConfigurationData | undefined {
+    if (
+      Configuration.configurationData == null &&
+      Configuration.configurationFile != null &&
+      Configuration.configurationFile.trim().length > 0
+    ) {
+      try {
+        Configuration.configurationData = JSON.parse(
+          readFileSync(Configuration.configurationFile, 'utf8')
+        ) as ConfigurationData
+        Configuration.configurationFileWatcher ??= Configuration.getConfigurationFileWatcher()
+      } catch (error) {
+        handleFileException(
+          Configuration.configurationFile,
+          FileType.Configuration,
+          error as NodeJS.ErrnoException,
+          logPrefix()
+        )
+      }
+    }
+    return Configuration.configurationData
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public static getConfigurationSection<T extends ConfigurationSectionType>(
     sectionName: ConfigurationSection
   ): T {
@@ -88,6 +167,12 @@ export class Configuration {
     return Configuration.getConfigurationData()?.stationTemplateUrls
   }
 
+  public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined {
+    return has('supervisionUrlDistribution', Configuration.getConfigurationData())
+      ? Configuration.getConfigurationData()?.supervisionUrlDistribution
+      : SupervisionUrlDistribution.ROUND_ROBIN
+  }
+
   public static getSupervisionUrls (): string | string[] | undefined {
     if (
       Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] != null
@@ -100,10 +185,11 @@ export class Configuration {
     return Configuration.getConfigurationData()?.supervisionUrls
   }
 
-  public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined {
-    return hasOwnProp(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
-      ? Configuration.getConfigurationData()?.supervisionUrlDistribution
-      : SupervisionUrlDistribution.ROUND_ROBIN
+  public static workerDynamicPoolInUse (): boolean {
+    return (
+      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
+        .processType === WorkerProcessType.dynamicPool
+    )
   }
 
   public static workerPoolInUse (): boolean {
@@ -114,94 +200,81 @@ export class Configuration {
     )
   }
 
-  public static workerDynamicPoolInUse (): boolean {
-    return (
-      Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
-        .processType === WorkerProcessType.dynamicPool
-    )
-  }
-
-  private static isConfigurationSectionCached (sectionName: ConfigurationSection): boolean {
-    return Configuration.configurationSectionCache.has(sectionName)
-  }
-
-  private static cacheConfigurationSection (sectionName: ConfigurationSection): void {
-    switch (sectionName) {
-      case ConfigurationSection.log:
-        Configuration.configurationSectionCache.set(sectionName, Configuration.buildLogSection())
-        break
-      case ConfigurationSection.performanceStorage:
-        Configuration.configurationSectionCache.set(
-          sectionName,
-          Configuration.buildPerformanceStorageSection()
-        )
-        break
-      case ConfigurationSection.worker:
-        Configuration.configurationSectionCache.set(sectionName, Configuration.buildWorkerSection())
-        break
-      case ConfigurationSection.uiServer:
-        Configuration.configurationSectionCache.set(
-          sectionName,
-          Configuration.buildUIServerSection()
-        )
-        break
-      default:
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        throw new Error(`Unknown configuration section '${sectionName}'`)
-    }
-  }
-
-  private static buildUIServerSection (): UIServerConfiguration {
-    let uiServerConfiguration: UIServerConfiguration = {
-      enabled: false,
-      type: ApplicationProtocol.WS,
-      version: ApplicationProtocolVersion.VERSION_11,
-      options: {
-        host: Constants.DEFAULT_UI_SERVER_HOST,
-        port: Constants.DEFAULT_UI_SERVER_PORT
-      }
-    }
-    if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.uiServer)) {
-      uiServerConfiguration = mergeDeepRight(
-        uiServerConfiguration,
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        Configuration.getConfigurationData()!.uiServer!
-      )
+  private static buildLogSection (): LogConfiguration {
+    const deprecatedLogConfiguration: LogConfiguration = {
+      ...(has('logEnabled', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        enabled: Configuration.getConfigurationData()?.logEnabled,
+      }),
+      ...(has('logFile', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        file: Configuration.getConfigurationData()?.logFile,
+      }),
+      ...(has('logErrorFile', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        errorFile: Configuration.getConfigurationData()?.logErrorFile,
+      }),
+      ...(has('logStatisticsInterval', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        statisticsInterval: Configuration.getConfigurationData()?.logStatisticsInterval,
+      }),
+      ...(has('logLevel', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        level: Configuration.getConfigurationData()?.logLevel,
+      }),
+      ...(has('logConsole', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        console: Configuration.getConfigurationData()?.logConsole,
+      }),
+      ...(has('logFormat', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        format: Configuration.getConfigurationData()?.logFormat,
+      }),
+      ...(has('logRotate', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        rotate: Configuration.getConfigurationData()?.logRotate,
+      }),
+      ...(has('logMaxFiles', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        maxFiles: Configuration.getConfigurationData()?.logMaxFiles,
+      }),
+      ...(has('logMaxSize', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        maxSize: Configuration.getConfigurationData()?.logMaxSize,
+      }),
     }
-    if (isCFEnvironment()) {
-      delete uiServerConfiguration.options?.host
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      uiServerConfiguration.options!.port = parseInt(env.PORT!)
+    const logConfiguration: LogConfiguration = {
+      ...defaultLogConfiguration,
+      ...deprecatedLogConfiguration,
+      ...(has(ConfigurationSection.log, Configuration.getConfigurationData()) &&
+        Configuration.getConfigurationData()?.log),
     }
-    return uiServerConfiguration
+    return logConfiguration
   }
 
   private static buildPerformanceStorageSection (): StorageConfiguration {
     let storageConfiguration: StorageConfiguration
     switch (Configuration.getConfigurationData()?.performanceStorage?.type) {
-      case StorageType.SQLITE:
+      case StorageType.JSON_FILE:
         storageConfiguration = {
           enabled: false,
-          type: StorageType.SQLITE,
-          uri: getDefaultPerformanceStorageUri(StorageType.SQLITE)
+          type: StorageType.JSON_FILE,
+          uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
         }
         break
-      case StorageType.JSON_FILE:
+      case StorageType.SQLITE:
         storageConfiguration = {
           enabled: false,
-          type: StorageType.JSON_FILE,
-          uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
+          type: StorageType.SQLITE,
+          uri: getDefaultPerformanceStorageUri(StorageType.SQLITE),
         }
         break
       case StorageType.NONE:
       default:
-        storageConfiguration = {
-          enabled: true,
-          type: StorageType.NONE
-        }
+        storageConfiguration = defaultStorageConfiguration
         break
     }
-    if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
+    if (has(ConfigurationSection.performanceStorage, Configuration.getConfigurationData())) {
       storageConfiguration = {
         ...storageConfiguration,
         ...Configuration.getConfigurationData()?.performanceStorage,
@@ -212,104 +285,69 @@ export class Configuration {
           uri: buildPerformanceUriFilePath(
             // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
             new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname
-          )
-        })
+          ),
+        }),
       }
     }
     return storageConfiguration
   }
 
-  private static buildLogSection (): LogConfiguration {
-    const defaultLogConfiguration: LogConfiguration = {
-      enabled: true,
-      file: 'logs/combined.log',
-      errorFile: 'logs/error.log',
-      statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
-      level: 'info',
-      format: 'simple',
-      rotate: true
-    }
-    const deprecatedLogConfiguration: LogConfiguration = {
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logEnabled') && {
-        enabled: Configuration.getConfigurationData()?.logEnabled
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logFile') && {
-        file: Configuration.getConfigurationData()?.logFile
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logErrorFile') && {
-        errorFile: Configuration.getConfigurationData()?.logErrorFile
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logStatisticsInterval') && {
-        statisticsInterval: Configuration.getConfigurationData()?.logStatisticsInterval
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logLevel') && {
-        level: Configuration.getConfigurationData()?.logLevel
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logConsole') && {
-        console: Configuration.getConfigurationData()?.logConsole
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logFormat') && {
-        format: Configuration.getConfigurationData()?.logFormat
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logRotate') && {
-        rotate: Configuration.getConfigurationData()?.logRotate
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxFiles') && {
-        maxFiles: Configuration.getConfigurationData()?.logMaxFiles
-      }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxSize') && {
-        maxSize: Configuration.getConfigurationData()?.logMaxSize
-      })
+  private static buildUIServerSection (): UIServerConfiguration {
+    let uiServerConfiguration: UIServerConfiguration = defaultUIServerConfiguration
+    if (has(ConfigurationSection.uiServer, Configuration.getConfigurationData())) {
+      uiServerConfiguration = mergeDeepRight(
+        uiServerConfiguration,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        Configuration.getConfigurationData()!.uiServer!
+      )
     }
-    const logConfiguration: LogConfiguration = {
-      ...defaultLogConfiguration,
-      ...deprecatedLogConfiguration,
-      ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.log) &&
-        Configuration.getConfigurationData()?.log)
+    if (isCFEnvironment()) {
+      delete uiServerConfiguration.options?.host
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      uiServerConfiguration.options!.port = Number.parseInt(env.PORT!)
     }
-    return logConfiguration
+    return uiServerConfiguration
   }
 
   private static buildWorkerSection (): WorkerConfiguration {
-    const defaultWorkerConfiguration: WorkerConfiguration = {
-      processType: WorkerProcessType.workerSet,
-      startDelay: DEFAULT_WORKER_START_DELAY,
-      elementsPerWorker: 'auto',
-      elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
-      poolMinSize: DEFAULT_POOL_MIN_SIZE,
-      poolMaxSize: DEFAULT_POOL_MAX_SIZE
-    }
-
     const deprecatedWorkerConfiguration: WorkerConfiguration = {
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'workerProcess') && {
-        processType: Configuration.getConfigurationData()?.workerProcess
+      ...(has('workerProcess', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        processType: Configuration.getConfigurationData()?.workerProcess,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'workerStartDelay') && {
-        startDelay: Configuration.getConfigurationData()?.workerStartDelay
+      ...(has('workerStartDelay', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        startDelay: Configuration.getConfigurationData()?.workerStartDelay,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'chargingStationsPerWorker') && {
-        elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker
+      ...(has('chargingStationsPerWorker', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'elementAddDelay') && {
-        elementAddDelay: Configuration.getConfigurationData()?.elementAddDelay
+      ...(has('elementAddDelay', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        elementAddDelay: Configuration.getConfigurationData()?.elementAddDelay,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData()?.worker, 'elementStartDelay') && {
-        elementAddDelay: Configuration.getConfigurationData()?.worker?.elementStartDelay
+      ...(has('elementStartDelay', Configuration.getConfigurationData()?.worker) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        elementAddDelay: Configuration.getConfigurationData()?.worker?.elementStartDelay,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMinSize') && {
-        poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize
+      ...(has('workerPoolMinSize', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize,
+      }),
+      ...(has('workerPoolMaxSize', Configuration.getConfigurationData()) && {
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        poolMaxSize: Configuration.getConfigurationData()?.workerPoolMaxSize,
       }),
-      ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMaxSize') && {
-        poolMaxSize: Configuration.getConfigurationData()?.workerPoolMaxSize
-      })
     }
-    hasOwnProp(Configuration.getConfigurationData(), 'workerPoolStrategy') &&
+    has('workerPoolStrategy', Configuration.getConfigurationData()) &&
+      // eslint-disable-next-line @typescript-eslint/no-deprecated
       delete Configuration.getConfigurationData()?.workerPoolStrategy
     const workerConfiguration: WorkerConfiguration = {
       ...defaultWorkerConfiguration,
       ...deprecatedWorkerConfiguration,
-      ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
-        Configuration.getConfigurationData()?.worker)
+      ...(has(ConfigurationSection.worker, Configuration.getConfigurationData()) &&
+        Configuration.getConfigurationData()?.worker),
     }
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     checkWorkerProcessType(workerConfiguration.processType!)
@@ -317,6 +355,32 @@ export class Configuration {
     return workerConfiguration
   }
 
+  private static cacheConfigurationSection (sectionName: ConfigurationSection): void {
+    switch (sectionName) {
+      case ConfigurationSection.log:
+        Configuration.configurationSectionCache.set(sectionName, Configuration.buildLogSection())
+        break
+      case ConfigurationSection.performanceStorage:
+        Configuration.configurationSectionCache.set(
+          sectionName,
+          Configuration.buildPerformanceStorageSection()
+        )
+        break
+      case ConfigurationSection.uiServer:
+        Configuration.configurationSectionCache.set(
+          sectionName,
+          Configuration.buildUIServerSection()
+        )
+        break
+      case ConfigurationSection.worker:
+        Configuration.configurationSectionCache.set(sectionName, Configuration.buildWorkerSection())
+        break
+      default:
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        throw new Error(`Unknown configuration section '${sectionName}'`)
+    }
+  }
+
   private static checkDeprecatedConfigurationKeys (): void {
     // connection timeout
     Configuration.warnDeprecatedConfigurationKey(
@@ -351,7 +415,6 @@ export class Configuration {
         ] as StationTemplateUrl[])
     Configuration.getConfigurationData()?.stationTemplateUrls.forEach(
       (stationTemplateUrl: StationTemplateUrl) => {
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
         if (stationTemplateUrl['numberOfStation' as keyof StationTemplateUrl] != null) {
           console.error(
             `${chalk.green(logPrefix())} ${chalk.red(
@@ -502,7 +565,7 @@ export class Configuration {
       "Use 'uri' instead"
     )
     // uiServer section
-    if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
+    if (has('uiWebSocketServer', Configuration.getConfigurationData())) {
       console.error(
         `${chalk.green(logPrefix())} ${chalk.red(
           `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`
@@ -511,61 +574,13 @@ export class Configuration {
     }
   }
 
-  private static warnDeprecatedConfigurationKey (
-    key: string,
-    configurationSection?: ConfigurationSection,
-    logMsgToAppend = ''
-  ): void {
+  private static getConfigurationFileWatcher (): FSWatcher | undefined {
     if (
-      configurationSection != null &&
-      Configuration.getConfigurationData()?.[configurationSection as keyof ConfigurationData] !=
-        null &&
-      (
-        Configuration.getConfigurationData()?.[
-          configurationSection as keyof ConfigurationData
-        ] as Record<string, unknown>
-      )[key] != null
+      Configuration.configurationFile == null ||
+      Configuration.configurationFile.trim().length === 0
     ) {
-      console.error(
-        `${chalk.green(logPrefix())} ${chalk.red(
-          `Deprecated configuration key '${key}' usage in section '${configurationSection}'${
-            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
-          }`
-        )}`
-      )
-    } else if (Configuration.getConfigurationData()?.[key as keyof ConfigurationData] != null) {
-      console.error(
-        `${chalk.green(logPrefix())} ${chalk.red(
-          `Deprecated configuration key '${key}' usage${
-            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
-          }`
-        )}`
-      )
+      return
     }
-  }
-
-  public static getConfigurationData (): ConfigurationData | undefined {
-    if (Configuration.configurationData == null) {
-      try {
-        Configuration.configurationData = JSON.parse(
-          readFileSync(Configuration.configurationFile, 'utf8')
-        ) as ConfigurationData
-        if (Configuration.configurationFileWatcher == null) {
-          Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher()
-        }
-      } catch (error) {
-        handleFileException(
-          Configuration.configurationFile,
-          FileType.Configuration,
-          error as NodeJS.ErrnoException,
-          logPrefix()
-        )
-      }
-    }
-    return Configuration.configurationData
-  }
-
-  private static getConfigurationFileWatcher (): FSWatcher | undefined {
     try {
       return watch(Configuration.configurationFile, (event, filename): void => {
         if (
@@ -578,19 +593,20 @@ export class Configuration {
           const consoleWarnOnce = once(console.warn)
           consoleWarnOnce(
             `${chalk.green(logPrefix())} ${chalk.yellow(
-              `${FileType.Configuration} ${this.configurationFile} file have changed, reload`
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `${FileType.Configuration} ${Configuration.configurationFile} file have changed, reload`
             )}`
           )
           delete Configuration.configurationData
           Configuration.configurationSectionCache.clear()
           if (Configuration.configurationChangeCallback != null) {
             Configuration.configurationChangeCallback()
-              .catch((error: unknown) => {
-                throw typeof error === 'string' ? new Error(error) : error
-              })
               .finally(() => {
                 Configuration.configurationFileReloading = false
               })
+              .catch((error: unknown) => {
+                throw typeof error === 'string' ? new Error(error) : error
+              })
           } else {
             Configuration.configurationFileReloading = false
           }
@@ -605,4 +621,41 @@ export class Configuration {
       )
     }
   }
+
+  private static isConfigurationSectionCached (sectionName: ConfigurationSection): boolean {
+    return Configuration.configurationSectionCache.has(sectionName)
+  }
+
+  private static warnDeprecatedConfigurationKey (
+    key: string,
+    configurationSection?: ConfigurationSection,
+    logMsgToAppend = ''
+  ): void {
+    if (
+      configurationSection != null &&
+      Configuration.getConfigurationData()?.[configurationSection as keyof ConfigurationData] !=
+        null &&
+      (
+        Configuration.getConfigurationData()?.[
+          configurationSection as keyof ConfigurationData
+        ] as Record<string, unknown>
+      )[key] != null
+    ) {
+      console.error(
+        `${chalk.green(logPrefix())} ${chalk.red(
+          `Deprecated configuration key '${key}' usage in section '${configurationSection}'${
+            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
+          }`
+        )}`
+      )
+    } else if (Configuration.getConfigurationData()?.[key as keyof ConfigurationData] != null) {
+      console.error(
+        `${chalk.green(logPrefix())} ${chalk.red(
+          `Deprecated configuration key '${key}' usage${
+            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
+          }`
+        )}`
+      )
+    }
+  }
 }
index 2a85bb0e2fd5686b603bf9a405181c3bc13f1b64..e76aa18c9b842ff78fec13e94f2bcddb8c2453ba 100644 (file)
@@ -1,8 +1,7 @@
+import chalk from 'chalk'
 import { dirname, join, resolve } from 'node:path'
 import { fileURLToPath } from 'node:url'
 
-import chalk from 'chalk'
-
 import { type ElementsPerWorkerType, type FileType, StorageType } from '../types/index.js'
 import { WorkerProcessType } from '../worker/index.js'
 import { Constants } from './Constants.js'
@@ -40,14 +39,14 @@ export const handleFileException = (
   const prefix = isNotEmptyString(logPfx) ? `${logPfx} ` : ''
   let logMsg: string
   switch (error.code) {
-    case 'ENOENT':
-      logMsg = `${fileType} file ${file} not found: `
+    case 'EACCES':
+      logMsg = `${fileType} file ${file} access denied: `
       break
     case 'EEXIST':
       logMsg = `${fileType} file ${file} already exists: `
       break
-    case 'EACCES':
-      logMsg = `${fileType} file ${file} access denied: `
+    case 'ENOENT':
+      logMsg = `${fileType} file ${file} not found: `
       break
     case 'EPERM':
       logMsg = `${fileType} file ${file} permission denied: `
@@ -77,12 +76,13 @@ export const checkWorkerElementsPerWorker = (
     !Number.isSafeInteger(elementsPerWorker)
   ) {
     throw new SyntaxError(
-      `Invalid number of elements per worker '${elementsPerWorker}' defined in configuration`
+      `Invalid number of elements per worker '${elementsPerWorker.toString()}' defined in configuration`
     )
   }
   if (Number.isSafeInteger(elementsPerWorker) && (elementsPerWorker as number) <= 0) {
     throw RangeError(
-      `Invalid negative or zero number of elements per worker '${elementsPerWorker}' defined in configuration`
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      `Invalid negative or zero number of elements per worker '${elementsPerWorker?.toString()}' defined in configuration`
     )
   }
 }
index 8103b47404cdaaf7c871caad5eaaa2586183bc4b..37025b3378d84a5b225e4b7e4451dfb797f4165b 100644 (file)
@@ -5,102 +5,101 @@ import {
   type IncomingRequestCommand,
   OCPPVersion,
   type RequestCommand,
-  VendorParametersKey
+  VendorParametersKey,
 } from '../types/index.js'
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class Constants {
-  // See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
-  private static readonly SEMVER_PATTERN =
-    '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$'
-
-  private static readonly DEFAULT_CHARGING_STATION_RESET_TIME = 30000 // Ms
-
-  static readonly DEFAULT_STATION_INFO: Partial<ChargingStationInfo> = Object.freeze({
-    enableStatistics: false,
-    autoStart: true,
-    remoteAuthorization: true,
-    currentOutType: CurrentType.AC,
-    mainVoltageMeterValues: true,
-    phaseLineToLineVoltageMeterValues: false,
-    customValueLimitationMeterValues: true,
-    ocppStrictCompliance: true,
-    outOfOrderEndMeterValues: false,
-    beginEndMeterValues: false,
-    meteringPerTransaction: true,
-    transactionDataMeterValues: false,
-    supervisionUrlOcppConfiguration: false,
-    supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl,
-    useConnectorId0: true,
-    ocppVersion: OCPPVersion.VERSION_16,
-    firmwareVersionPattern: Constants.SEMVER_PATTERN,
-    firmwareUpgrade: {
-      versionUpgrade: {
-        step: 1
-      },
-      reset: true
-    },
-    ocppPersistentConfiguration: true,
-    stationInfoPersistentConfiguration: true,
-    automaticTransactionGeneratorPersistentConfiguration: true,
-    resetTime: Constants.DEFAULT_CHARGING_STATION_RESET_TIME,
-    autoReconnectMaxRetries: -1,
-    registrationMaxRetries: -1,
-    reconnectExponentialDelay: false,
-    stopTransactionsOnStopped: true
-  })
-
-  static readonly DEFAULT_BOOT_NOTIFICATION_INTERVAL = 60000 // Ms
-  static readonly DEFAULT_HEARTBEAT_INTERVAL = 60000 // Ms
-  static readonly DEFAULT_METER_VALUES_INTERVAL = 60000 // Ms
-
-  static readonly DEFAULT_ATG_WAIT_TIME = 1000 // Ms
   static readonly DEFAULT_ATG_CONFIGURATION: AutomaticTransactionGeneratorConfiguration =
     Object.freeze({
       enable: false,
-      minDuration: 60,
+      maxDelayBetweenTwoTransactions: 30,
       maxDuration: 120,
       minDelayBetweenTwoTransactions: 15,
-      maxDelayBetweenTwoTransactions: 30,
+      minDuration: 60,
       probabilityOfStart: 1,
+      stopAbsoluteDuration: false,
       stopAfterHours: 0.25,
-      stopAbsoluteDuration: false
     })
 
-  static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 385
+  static readonly DEFAULT_ATG_WAIT_TIME = 1000 // Ms
+
+  static readonly DEFAULT_BOOT_NOTIFICATION_INTERVAL = 60000 // Ms
 
+  static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 386
+  static readonly DEFAULT_CONNECTION_TIMEOUT = 30
+
+  static readonly DEFAULT_FLUCTUATION_PERCENT = 5
   static readonly DEFAULT_HASH_ALGORITHM = 'sha384'
 
-  static readonly DEFAULT_IDTAG = '00000000'
+  static readonly DEFAULT_HEARTBEAT_INTERVAL = 60000 // Ms
 
-  static readonly DEFAULT_CONNECTION_TIMEOUT = 30
+  static readonly DEFAULT_IDTAG = '00000000'
 
   static readonly DEFAULT_LOG_STATISTICS_INTERVAL = 60 // Seconds
 
-  static readonly DEFAULT_FLUCTUATION_PERCENT = 5
+  static readonly DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL = 60000 // Ms
+
+  static readonly DEFAULT_METER_VALUES_INTERVAL = 60000 // Ms
 
   static readonly DEFAULT_PERFORMANCE_DIRECTORY = 'performance'
-  static readonly DEFAULT_PERFORMANCE_RECORDS_FILENAME = 'performanceRecords.json'
+
   static readonly DEFAULT_PERFORMANCE_RECORDS_DB_NAME = 'e-mobility-charging-stations-simulator'
-  static readonly PERFORMANCE_RECORDS_TABLE = 'performance_records'
+  static readonly DEFAULT_PERFORMANCE_RECORDS_FILENAME = 'performanceRecords.json'
+  static readonly DEFAULT_STATION_INFO: Readonly<Partial<ChargingStationInfo>> = Object.freeze({
+    automaticTransactionGeneratorPersistentConfiguration: true,
+    autoReconnectMaxRetries: -1,
+    autoStart: true,
+    beginEndMeterValues: false,
+    currentOutType: CurrentType.AC,
+    customValueLimitationMeterValues: true,
+    enableStatistics: false,
+    firmwareUpgrade: {
+      reset: true,
+      versionUpgrade: {
+        step: 1,
+      },
+    },
+    // See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
+    firmwareVersionPattern:
+      '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$',
+    mainVoltageMeterValues: true,
+    meteringPerTransaction: true,
+    ocppPersistentConfiguration: true,
+    ocppStrictCompliance: true,
+    ocppVersion: OCPPVersion.VERSION_16,
+    outOfOrderEndMeterValues: false,
+    phaseLineToLineVoltageMeterValues: false,
+    reconnectExponentialDelay: false,
+    registrationMaxRetries: -1,
+    remoteAuthorization: true,
+    resetTime: 30000, // Ms
+    stationInfoPersistentConfiguration: true,
+    stopTransactionsOnStopped: true,
+    supervisionUrlOcppConfiguration: false,
+    supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl,
+    transactionDataMeterValues: false,
+    useConnectorId0: true,
+  })
 
   static readonly DEFAULT_UI_SERVER_HOST = 'localhost'
+
   static readonly DEFAULT_UI_SERVER_PORT = 8080
+  static readonly EMPTY_FROZEN_OBJECT = Object.freeze({})
 
-  static readonly UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' as
-    | RequestCommand
-    | IncomingRequestCommand
+  static readonly EMPTY_FUNCTION = Object.freeze(() => {
+    /* This is intentional */
+  })
 
   static readonly MAX_RANDOM_INTEGER = 281474976710655
 
-  static readonly STOP_CHARGING_STATIONS_TIMEOUT = 60000 // Ms
+  static readonly PERFORMANCE_RECORDS_TABLE = 'performance_records'
 
-  static readonly EMPTY_FROZEN_OBJECT = Object.freeze({})
-  static readonly EMPTY_FUNCTION = Object.freeze(() => {
-    /* This is intentional */
-  })
+  static readonly STOP_CHARGING_STATIONS_TIMEOUT = 60000 // Ms
 
-  static readonly DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL = 60000 // Ms
+  static readonly UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' as
+    | IncomingRequestCommand
+    | RequestCommand
 
   private constructor () {
     // This is intentional
index c4fdfa10142c1d7fde8a1fdb685686813f20d91d..923c6849be00268ad41bf1a88480c95ffef69084 100644 (file)
@@ -13,16 +13,13 @@ export class ACElectricUtils {
     // This is intentional
   }
 
-  static powerTotal (nbOfPhases: number, V: number, Iph: number, cosPhi = 1): number {
-    return nbOfPhases * ACElectricUtils.powerPerPhase(V, Iph, cosPhi)
-  }
-
-  static powerPerPhase (V: number, Iph: number, cosPhi = 1): number {
-    const powerPerPhase = V * Iph * cosPhi
-    if (cosPhi === 1) {
-      return powerPerPhase
+  static amperagePerPhaseFromPower (nbOfPhases: number, P: number, V: number, cosPhi = 1): number {
+    const amperage = ACElectricUtils.amperageTotalFromPower(P, V, cosPhi)
+    const amperagePerPhase = amperage / nbOfPhases
+    if (amperage % nbOfPhases === 0) {
+      return amperagePerPhase
     }
-    return Math.round(powerPerPhase)
+    return Math.round(amperagePerPhase)
   }
 
   static amperageTotal (nbOfPhases: number, Iph: number): number {
@@ -37,13 +34,16 @@ export class ACElectricUtils {
     return Math.round(amperage)
   }
 
-  static amperagePerPhaseFromPower (nbOfPhases: number, P: number, V: number, cosPhi = 1): number {
-    const amperage = ACElectricUtils.amperageTotalFromPower(P, V, cosPhi)
-    const amperagePerPhase = amperage / nbOfPhases
-    if (amperage % nbOfPhases === 0) {
-      return amperagePerPhase
+  static powerPerPhase (V: number, Iph: number, cosPhi = 1): number {
+    const powerPerPhase = V * Iph * cosPhi
+    if (cosPhi === 1) {
+      return powerPerPhase
     }
-    return Math.round(amperagePerPhase)
+    return Math.round(powerPerPhase)
+  }
+
+  static powerTotal (nbOfPhases: number, V: number, Iph: number, cosPhi = 1): number {
+    return nbOfPhases * ACElectricUtils.powerPerPhase(V, Iph, cosPhi)
   }
 }
 
@@ -56,10 +56,6 @@ export class DCElectricUtils {
     // This is intentional
   }
 
-  static power (V: number, I: number): number {
-    return V * I
-  }
-
   static amperage (P: number, V: number): number {
     const amperage = P / V
     if (P % V === 0) {
@@ -67,4 +63,8 @@ export class DCElectricUtils {
     }
     return Math.round(amperage)
   }
+
+  static power (V: number, I: number): number {
+    return V * I
+  }
 }
index 9c243a299e769f67c4e70859aae110651a3444cd..808c2b5390cb1bcd81d358d1fb6f78bf63ab5186 100644 (file)
@@ -1,6 +1,5 @@
-import process from 'node:process'
-
 import chalk from 'chalk'
+import process from 'node:process'
 
 import type { ChargingStation } from '../charging-station/index.js'
 import type {
@@ -9,15 +8,15 @@ import type {
   HandleErrorParams,
   IncomingRequestCommand,
   JsonType,
-  RequestCommand
+  MessageType,
+  RequestCommand,
 } from '../types/index.js'
+
+import { getMessageTypeString } from '../charging-station/ocpp/OCPPServiceUtils.js'
 import { logger } from './Logger.js'
 import { isNotEmptyString } from './Utils.js'
 
-const defaultErrorParams = {
-  throwError: true,
-  consoleOut: false
-} satisfies HandleErrorParams<EmptyObject>
+const moduleName = 'ErrorUtils'
 
 export const handleUncaughtException = (): void => {
   process.on('uncaughtException', (error: Error) => {
@@ -36,20 +35,26 @@ export const handleFileException = (
   fileType: FileType,
   error: NodeJS.ErrnoException,
   logPrefix: string,
-  params: HandleErrorParams<EmptyObject> = defaultErrorParams
+  params?: HandleErrorParams<EmptyObject>
 ): void => {
-  setDefaultErrorParams(params)
+  params = {
+    ...{
+      consoleOut: false,
+      throwError: true,
+    },
+    ...params,
+  }
   const prefix = isNotEmptyString(logPrefix) ? `${logPrefix} ` : ''
   let logMsg: string
   switch (error.code) {
-    case 'ENOENT':
-      logMsg = `${fileType} file ${file} not found:`
+    case 'EACCES':
+      logMsg = `${fileType} file ${file} access denied:`
       break
     case 'EEXIST':
       logMsg = `${fileType} file ${file} already exists:`
       break
-    case 'EACCES':
-      logMsg = `${fileType} file ${file} access denied:`
+    case 'ENOENT':
+      logMsg = `${fileType} file ${file} not found:`
       break
     case 'EPERM':
       logMsg = `${fileType} file ${file} permission denied:`
@@ -78,21 +83,51 @@ export const handleFileException = (
 
 export const handleSendMessageError = (
   chargingStation: ChargingStation,
-  commandName: RequestCommand | IncomingRequestCommand,
+  commandName: IncomingRequestCommand | RequestCommand,
+  messageType: MessageType,
   error: Error,
-  params: HandleErrorParams<EmptyObject> = { throwError: false, consoleOut: false }
+  params?: HandleErrorParams<EmptyObject>
 ): void => {
-  setDefaultErrorParams(params, { throwError: false, consoleOut: false })
-  logger.error(`${chargingStation.logPrefix()} Request command '${commandName}' error:`, error)
+  params = {
+    ...{
+      consoleOut: false,
+      throwError: false,
+    },
+    ...params,
+  }
+  logger.error(
+    `${chargingStation.logPrefix()} ${moduleName}.handleSendMessageError: Send ${getMessageTypeString(messageType)} command '${commandName}' error:`,
+    error
+  )
   if (params.throwError === true) {
     throw error
   }
 }
 
-export const setDefaultErrorParams = <T extends JsonType>(
-  params: HandleErrorParams<T>,
-  defaultParams: HandleErrorParams<T> = defaultErrorParams
-): HandleErrorParams<T> => {
-  params = { ...defaultParams, ...params }
-  return params
+export const handleIncomingRequestError = <T extends JsonType>(
+  chargingStation: ChargingStation,
+  commandName: IncomingRequestCommand,
+  error: Error,
+  params?: HandleErrorParams<T>
+): T | undefined => {
+  params = {
+    ...{
+      consoleOut: false,
+      throwError: true,
+    },
+    ...params,
+  }
+  logger.error(
+    `${chargingStation.logPrefix()} ${moduleName}.handleIncomingRequestError: Incoming request command '${commandName}' error:`,
+    error
+  )
+  if (params.throwError === false && params.errorResponse != null) {
+    return params.errorResponse
+  }
+  if (params.throwError === true && params.errorResponse == null) {
+    throw error
+  }
+  if (params.throwError === true && params.errorResponse != null) {
+    return params.errorResponse
+  }
 }
index 543a2b23c2eda50f157680f15377ee335ec697da..f52a4df7ef3ee5f45f2eeb10c40e1f2b900c9e9b 100644 (file)
@@ -1,6 +1,7 @@
 import { type FSWatcher, readFileSync, watch, type WatchListener } from 'node:fs'
 
 import type { FileType, JsonType } from '../types/index.js'
+
 import { handleFileException } from './ErrorUtils.js'
 import { logger } from './Logger.js'
 import { isNotEmptyString } from './Utils.js'
@@ -18,7 +19,7 @@ export const watchJsonFile = <T extends JsonType>(
           (refreshedVariable = JSON.parse(readFileSync(file, 'utf8')) as T)
       } catch (error) {
         handleFileException(file, fileType, error as NodeJS.ErrnoException, logPrefix, {
-          throwError: false
+          throwError: false,
         })
       }
     }
@@ -29,7 +30,7 @@ export const watchJsonFile = <T extends JsonType>(
       return watch(file, listener)
     } catch (error) {
       handleFileException(file, fileType, error as NodeJS.ErrnoException, logPrefix, {
-        throwError: false
+        throwError: false,
       })
     }
   } else {
index b13ec0477ab9314aca3859f264e62c40ae531929..8ee1d8abdf94ccfee707948526900b54394c3508 100644 (file)
@@ -1,7 +1,8 @@
 import type { FormatWrap } from 'logform'
+
 import { createLogger, format, type transport } from 'winston'
-import TransportType from 'winston/lib/winston/transports/index.js'
 import DailyRotateFile from 'winston-daily-rotate-file'
+import TransportType from 'winston/lib/winston/transports/index.js'
 
 import { ConfigurationSection, type LogConfiguration } from '../types/index.js'
 import { Configuration } from './Configuration.js'
@@ -25,36 +26,36 @@ if (logConfiguration.rotate === true) {
       ),
       level: 'error',
       ...(logMaxFiles != null && { maxFiles: logMaxFiles }),
-      ...(logMaxSize != null && { maxSize: logMaxSize })
+      ...(logMaxSize != null && { maxSize: logMaxSize }),
     }),
     new DailyRotateFile({
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       filename: insertAt(logConfiguration.file!, '-%DATE%', logConfiguration.file!.indexOf('.log')),
       ...(logMaxFiles != null && { maxFiles: logMaxFiles }),
-      ...(logMaxSize != null && { maxSize: logMaxSize })
-    })
+      ...(logMaxSize != null && { maxSize: logMaxSize }),
+    }),
   ]
 } else {
   transports = [
     new TransportType.File({
       filename: logConfiguration.errorFile,
-      level: 'error'
+      level: 'error',
     }),
     new TransportType.File({
-      filename: logConfiguration.file
-    })
+      filename: logConfiguration.file,
+    }),
   ]
 }
 
-export const logger = createLogger({
-  silent: logConfiguration.enabled === false,
-  level: logConfiguration.level,
+const logger = createLogger({
   format: format.combine(
     format.splat(),
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     (format[logConfiguration.format! as keyof FormatWrap] as FormatWrap)()
   ),
-  transports
+  level: logConfiguration.level,
+  silent: logConfiguration.enabled === false,
+  transports,
 })
 
 //
@@ -68,7 +69,9 @@ if (logConfiguration.console === true) {
         format.splat(),
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         (format[logConfiguration.format! as keyof FormatWrap] as FormatWrap)()
-      )
+      ),
     })
   )
 }
+
+export { logger }
index 2fb952560b49e25b82cfd50fff951cbfe27bcccf..f1ca57c1d50f0d7a482d589d2471021606f94085 100644 (file)
@@ -1,25 +1,27 @@
-import { clone } from 'rambda'
+import { CircularBuffer } from 'mnemonist'
 
 import type { ChargingStation } from '../charging-station/index.js'
+
 import {
   type ChargingStationData,
   type ChargingStationWorkerMessage,
   ChargingStationWorkerMessageEvents,
-  type Statistics
+  type Statistics,
+  type TimestampedData,
 } from '../types/index.js'
 import {
   buildChargingStationAutomaticTransactionGeneratorConfiguration,
   buildConnectorsStatus,
   buildEvsesStatus,
-  OutputFormat
+  OutputFormat,
 } from './ChargingStationConfigurationUtils.js'
 
 export const buildAddedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
   return {
+    data: buildChargingStationDataPayload(chargingStation),
     event: ChargingStationWorkerMessageEvents.added,
-    data: buildChargingStationDataPayload(chargingStation)
   }
 }
 
@@ -27,8 +29,8 @@ export const buildDeletedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
   return {
+    data: buildChargingStationDataPayload(chargingStation),
     event: ChargingStationWorkerMessageEvents.deleted,
-    data: buildChargingStationDataPayload(chargingStation)
   }
 }
 
@@ -36,8 +38,8 @@ export const buildStartedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
   return {
+    data: buildChargingStationDataPayload(chargingStation),
     event: ChargingStationWorkerMessageEvents.started,
-    data: buildChargingStationDataPayload(chargingStation)
   }
 }
 
@@ -45,8 +47,8 @@ export const buildStoppedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
   return {
+    data: buildChargingStationDataPayload(chargingStation),
     event: ChargingStationWorkerMessageEvents.stopped,
-    data: buildChargingStationDataPayload(chargingStation)
   }
 }
 
@@ -54,36 +56,48 @@ export const buildUpdatedMessage = (
   chargingStation: ChargingStation
 ): ChargingStationWorkerMessage<ChargingStationData> => {
   return {
+    data: buildChargingStationDataPayload(chargingStation),
     event: ChargingStationWorkerMessageEvents.updated,
-    data: buildChargingStationDataPayload(chargingStation)
   }
 }
 
 export const buildPerformanceStatisticsMessage = (
   statistics: Statistics
 ): ChargingStationWorkerMessage<Statistics> => {
+  const statisticsData = [...statistics.statisticsData].map(([key, value]) => {
+    if (value.measurementTimeSeries instanceof CircularBuffer) {
+      value.measurementTimeSeries = value.measurementTimeSeries.toArray() as TimestampedData[]
+    }
+    return [key, value]
+  })
   return {
+    data: {
+      createdAt: statistics.createdAt,
+      id: statistics.id,
+      name: statistics.name,
+      statisticsData,
+      updatedAt: statistics.updatedAt,
+      uri: statistics.uri,
+    },
     event: ChargingStationWorkerMessageEvents.performanceStatistics,
-    // FIXME: CircularBuffer is not structured-cloneable, rambda clone strips the whole statisticsData Map
-    data: clone(statistics)
   }
 }
 
 const buildChargingStationDataPayload = (chargingStation: ChargingStation): ChargingStationData => {
   return {
-    started: chargingStation.started,
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    stationInfo: chargingStation.stationInfo!,
+    bootNotificationResponse: chargingStation.bootNotificationResponse,
     connectors: buildConnectorsStatus(chargingStation),
     evses: buildEvsesStatus(chargingStation, OutputFormat.worker),
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     ocppConfiguration: chargingStation.ocppConfiguration!,
+    started: chargingStation.started,
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    stationInfo: chargingStation.stationInfo!,
     supervisionUrl: chargingStation.wsConnectionUrl.href,
     wsState: chargingStation.wsConnection?.readyState,
-    bootNotificationResponse: chargingStation.bootNotificationResponse,
     ...(chargingStation.automaticTransactionGenerator != null && {
       automaticTransactionGenerator:
-        buildChargingStationAutomaticTransactionGeneratorConfiguration(chargingStation)
-    })
+        buildChargingStationAutomaticTransactionGeneratorConfiguration(chargingStation),
+    }),
   }
 }
index 501dea7797ec30ecc469604ed1e93697551ad095..2c65759a62926fae7cdef1a1a7fba680fbe4d620 100644 (file)
@@ -1,4 +1,25 @@
-import { mean } from 'rambda'
+export const average = (dataSet: number[]): number => {
+  if (Array.isArray(dataSet) && dataSet.length === 0) {
+    return 0
+  }
+  if (Array.isArray(dataSet) && dataSet.length === 1) {
+    return dataSet[0]
+  }
+  return dataSet.reduce((accumulator, number) => accumulator + number, 0) / dataSet.length
+}
+
+export const median = (dataSet: number[]): number => {
+  if (Array.isArray(dataSet) && dataSet.length === 0) {
+    return 0
+  }
+  if (Array.isArray(dataSet) && dataSet.length === 1) {
+    return dataSet[0]
+  }
+  const sortedDataSet = dataSet.slice().sort((a, b) => a - b)
+  return (
+    (sortedDataSet[(sortedDataSet.length - 1) >> 1] + sortedDataSet[sortedDataSet.length >> 1]) / 2
+  )
+}
 
 export const min = (...args: number[]): number =>
   args.reduce((minimum, num) => (minimum < num ? minimum : num), Number.POSITIVE_INFINITY)
@@ -7,7 +28,7 @@ export const max = (...args: number[]): number =>
   args.reduce((maximum, num) => (maximum > num ? maximum : num), Number.NEGATIVE_INFINITY)
 
 // TODO: use order statistics tree https://en.wikipedia.org/wiki/Order_statistic_tree
-export const nthPercentile = (dataSet: number[], percentile: number): number => {
+export const percentile = (dataSet: number[], percentile: number): number => {
   if (percentile < 0 && percentile > 100) {
     throw new RangeError('Percentile is not between 0 and 100')
   }
@@ -35,19 +56,18 @@ export const nthPercentile = (dataSet: number[], percentile: number): number =>
 
 /**
  * Computes the sample standard deviation of the given data set.
- *
  * @param dataSet - Data set.
  * @param dataSetAverage - Average of the data set.
  * @returns The sample standard deviation of the given data set.
  * @see https://en.wikipedia.org/wiki/Unbiased_estimation_of_standard_deviation
  * @internal
  */
-export const stdDeviation = (dataSet: number[], dataSetAverage: number = mean(dataSet)): number => {
+export const std = (dataSet: number[], dataSetAverage: number = average(dataSet)): number => {
   if (Array.isArray(dataSet) && (dataSet.length === 0 || dataSet.length === 1)) {
     return 0
   }
   return Math.sqrt(
-    dataSet.reduce((accumulator, num) => accumulator + Math.pow(num - dataSetAverage, 2), 0) /
+    dataSet.reduce((accumulator, num) => accumulator + (num - dataSetAverage) ** 2, 0) /
       (dataSet.length - 1)
   )
 }
index fe059e0688cd5672f8e25fd977113aa394b7bb11..c8a8b14d3fe54e358b695c3159f933bcab9ecb9f 100644 (file)
@@ -1,5 +1,4 @@
-import { getRandomValues, randomBytes, randomUUID } from 'node:crypto'
-import { env, nextTick } from 'node:process'
+import type { CircularBuffer } from 'mnemonist'
 
 import {
   formatDuration,
@@ -10,22 +9,108 @@ import {
   millisecondsToMinutes,
   millisecondsToSeconds,
   minutesToSeconds,
-  secondsToMilliseconds
+  secondsToMilliseconds,
 } from 'date-fns'
-import type { CircularBuffer } from 'mnemonist'
-import { is } from 'rambda'
+import { getRandomValues, randomBytes, randomUUID } from 'node:crypto'
+import { env } from 'node:process'
 
 import {
   type JsonType,
   MapStringifyFormat,
   type TimestampedData,
-  WebSocketCloseEventStatusString
+  WebSocketCloseEventStatusString,
 } from '../types/index.js'
 
+type NonEmptyArray<T> = [T, ...T[]]
+type ReadonlyNonEmptyArray<T> = readonly [T, ...(readonly T[])]
+
 export const logPrefix = (prefixString = ''): string => {
   return `${new Date().toLocaleString()}${prefixString}`
 }
 
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const once = <T extends (...args: any[]) => any>(fn: T): T => {
+  let hasBeenCalled = false
+  let result: ReturnType<T>
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  return function (this: any, ...args: Parameters<T>): ReturnType<T> {
+    if (!hasBeenCalled) {
+      hasBeenCalled = true
+      result = fn.apply(this, args) as ReturnType<T>
+    }
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+    return result
+  } as T
+}
+
+export const has = (property: PropertyKey, object: null | object | undefined): boolean => {
+  if (object == null) {
+    return false
+  }
+  return Object.hasOwn(object, property)
+}
+
+const type = (value: unknown): string => {
+  if (value === null) return 'Null'
+  if (value === undefined) return 'Undefined'
+  if (Number.isNaN(value)) return 'NaN'
+  if (Array.isArray(value)) return 'Array'
+  return Object.prototype.toString.call(value).slice(8, -1)
+}
+
+export const isEmpty = (value: unknown): boolean => {
+  const valueType = type(value)
+  if (['NaN', 'Null', 'Number', 'Undefined'].includes(valueType)) {
+    return false
+  }
+  if (!value) return true
+
+  if (valueType === 'Object') {
+    return Object.keys(value as Record<string, unknown>).length === 0
+  }
+
+  if (valueType === 'Array') {
+    return (value as unknown[]).length === 0
+  }
+
+  if (valueType === 'Map') {
+    return (value as Map<unknown, unknown>).size === 0
+  }
+
+  if (valueType === 'Set') {
+    return (value as Set<unknown>).size === 0
+  }
+
+  return false
+}
+
+const isObject = (value: unknown): value is object => {
+  return type(value) === 'Object'
+}
+
+export const mergeDeepRight = <T extends Record<string, unknown>>(
+  target: T,
+  source: Partial<T>
+): T => {
+  const output = { ...target }
+
+  if (isObject(target) && isObject(source)) {
+    Object.keys(source).forEach(key => {
+      if (isObject(source[key])) {
+        if (!(key in target)) {
+          Object.assign(output, { [key]: source[key] })
+        } else {
+          output[key] = mergeDeepRight(target[key], source[key])
+        }
+      } else {
+        Object.assign(output, { [key]: source[key] })
+      }
+    })
+  }
+
+  return output
+}
+
 export const generateUUID = (): `${string}-${string}-${string}-${string}-${string}` => {
   return randomUUID()
 }
@@ -65,7 +150,7 @@ export const formatDurationMilliSeconds = (duration: number): string => {
     days,
     hours,
     minutes,
-    seconds
+    seconds,
   })
 }
 
@@ -76,15 +161,15 @@ export const formatDurationSeconds = (duration: number): string => {
 // More efficient time validation function than the one provided by date-fns
 export const isValidDate = (date: Date | number | undefined): date is Date | number => {
   if (typeof date === 'number') {
-    return !isNaN(date)
+    return !Number.isNaN(date)
   } else if (isDate(date)) {
-    return !isNaN(date.getTime())
+    return !Number.isNaN(date.getTime())
   }
   return false
 }
 
 export const convertToDate = (
-  value: Date | string | number | undefined | null
+  value: Date | null | number | string | undefined
 ): Date | undefined => {
   if (value == null) {
     return undefined
@@ -94,8 +179,8 @@ export const convertToDate = (
   }
   if (typeof value === 'string' || typeof value === 'number') {
     const valueToDate = new Date(value)
-    if (isNaN(valueToDate.getTime())) {
-      throw new Error(`Cannot convert to date: '${value}'`)
+    if (Number.isNaN(valueToDate.getTime())) {
+      throw new Error(`Cannot convert to date: '${value.toString()}'`)
     }
     return valueToDate
   }
@@ -113,10 +198,11 @@ export const convertToInt = (value: unknown): number => {
   }
   let changedValue: number = value as number
   if (typeof value === 'string') {
-    changedValue = parseInt(value)
+    changedValue = Number.parseInt(value)
   }
-  if (isNaN(changedValue)) {
-    throw new Error(`Cannot convert to integer: '${String(value)}'`)
+  if (Number.isNaN(changedValue)) {
+    // eslint-disable-next-line @typescript-eslint/no-base-to-string
+    throw new Error(`Cannot convert to integer: '${value.toString()}'`)
   }
   return changedValue
 }
@@ -127,10 +213,11 @@ export const convertToFloat = (value: unknown): number => {
   }
   let changedValue: number = value as number
   if (typeof value === 'string') {
-    changedValue = parseFloat(value)
+    changedValue = Number.parseFloat(value)
   }
-  if (isNaN(changedValue)) {
-    throw new Error(`Cannot convert to float: '${String(value)}'`)
+  if (Number.isNaN(changedValue)) {
+    // eslint-disable-next-line @typescript-eslint/no-base-to-string
+    throw new Error(`Cannot convert to float: '${value.toString()}'`)
   }
   return changedValue
 }
@@ -163,13 +250,12 @@ export const getRandomFloat = (max = Number.MAX_VALUE, min = 0): number => {
 /**
  * Rounds the given number to the given scale.
  * The rounding is done using the "round half away from zero" method.
- *
  * @param numberValue - The number to round.
  * @param scale - The scale to round to.
  * @returns The rounded number.
  */
 export const roundTo = (numberValue: number, scale: number): number => {
-  const roundPower = Math.pow(10, scale)
+  const roundPower = 10 ** scale
   return Math.round(numberValue * roundPower * (1 + Number.EPSILON)) / roundPower
 }
 
@@ -187,7 +273,7 @@ export const getRandomFloatFluctuatedRounded = (
 ): number => {
   if (fluctuationPercent < 0 || fluctuationPercent > 100) {
     throw new RangeError(
-      `Fluctuation percent must be between 0 and 100. Actual value: ${fluctuationPercent}`
+      `Fluctuation percent must be between 0 and 100. Actual value: ${fluctuationPercent.toString()}`
     )
   }
   if (fluctuationPercent === 0) {
@@ -209,34 +295,32 @@ export const clone = <T>(object: T): T => {
   return structuredClone<T>(object)
 }
 
+type AsyncFunctionType<A extends unknown[], R> = (...args: A) => PromiseLike<R>
+
 /**
  * Detects whether the given value is an asynchronous function or not.
- *
  * @param fn - Unknown value.
  * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
  * @internal
  */
-export const isAsyncFunction = (fn: unknown): fn is (...args: unknown[]) => Promise<unknown> => {
-  return is(Function, fn) && fn.constructor.name === 'AsyncFunction'
-}
-
-export const isObject = (value: unknown): value is object => {
-  return value != null && !Array.isArray(value) && is(Object, value)
-}
-
-export const hasOwnProp = (value: unknown, property: PropertyKey): boolean => {
-  return isObject(value) && Object.hasOwn(value, property)
+export const isAsyncFunction = (fn: unknown): fn is AsyncFunctionType<unknown[], unknown> => {
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  return fn?.constructor === (async () => {}).constructor
 }
 
 export const isCFEnvironment = (): boolean => {
   return env.VCAP_APPLICATION != null
 }
 
-export const isNotEmptyString = (value: unknown): value is string => {
+declare const nonEmptyString: unique symbol
+type NonEmptyString = string & { [nonEmptyString]: true }
+export const isNotEmptyString = (value: unknown): value is NonEmptyString => {
   return typeof value === 'string' && value.trim().length > 0
 }
 
-export const isNotEmptyArray = (value: unknown): value is unknown[] => {
+export const isNotEmptyArray = <T>(
+  value: unknown
+): value is NonEmptyArray<T> | ReadonlyNonEmptyArray<T> => {
   return Array.isArray(value) && value.length > 0
 }
 
@@ -245,20 +329,18 @@ export const insertAt = (str: string, subStr: string, pos: number): string =>
 
 /**
  * Computes the retry delay in milliseconds using an exponential backoff algorithm.
- *
  * @param retryNumber - the number of retries that have already been attempted
  * @param delayFactor - the base delay factor in milliseconds
  * @returns delay in milliseconds
  */
 export const exponentialDelay = (retryNumber = 0, delayFactor = 100): number => {
-  const delay = Math.pow(2, retryNumber) * delayFactor
+  const delay = 2 ** retryNumber * delayFactor
   const randomSum = delay * 0.2 * secureRandom() // 0-20% of the delay
   return delay + randomSum
 }
 
 /**
  * Generates a cryptographically secure random number in the [0,1[ range
- *
  * @returns A number in the [0,1[ range
  */
 export const secureRandom = (): number => {
@@ -266,29 +348,32 @@ export const secureRandom = (): number => {
 }
 
 export const JSONStringify = <
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   T extends
-  | JsonType
-  | Array<Record<string, unknown>>
-  | Set<Record<string, unknown>>
-  | Map<string, Record<string, unknown>>
+    | JsonType
+    | Map<string, Record<string, unknown>>
+    | Record<string, unknown>[]
+    | Set<Record<string, unknown>>
 >(
     object: T,
-    space?: string | number,
+    space?: number | string,
     mapFormat?: MapStringifyFormat
   ): string => {
   return JSON.stringify(
     object,
     (_, value: Record<string, unknown>) => {
-      if (is(Map, value)) {
+      if (value instanceof Map) {
         switch (mapFormat) {
           case MapStringifyFormat.object:
-            return { ...Object.fromEntries<Map<string, Record<string, unknown>>>(value.entries()) }
+            return {
+              ...Object.fromEntries<Map<string, Record<string, unknown>>>(value.entries()),
+            }
           case MapStringifyFormat.array:
           default:
             return [...value]
         }
-      } else if (is(Set, value)) {
-        return [...value] as JsonType[]
+      } else if (value instanceof Set) {
+        return [...value] as Record<string, unknown>[]
       }
       return value
     },
@@ -298,7 +383,6 @@ export const JSONStringify = <
 
 /**
  * Converts websocket error code to human readable string message
- *
  * @param code - websocket error code
  * @returns human readable string message
  */
@@ -326,6 +410,9 @@ export const getWebSocketCloseEventStatusString = (code: number): string => {
 }
 
 export const isArraySorted = <T>(array: T[], compareFn: (a: T, b: T) => number): boolean => {
+  if (array.length <= 1) {
+    return true
+  }
   for (let index = 0; index < array.length - 1; ++index) {
     if (compareFn(array[index], array[index + 1]) > 0) {
       return false
@@ -334,8 +421,8 @@ export const isArraySorted = <T>(array: T[], compareFn: (a: T, b: T) => number):
   return true
 }
 
-export const throwErrorInNextTick = (error: Error): void => {
-  nextTick(() => {
+export const queueMicrotaskErrorThrowing = (error: Error): void => {
+  queueMicrotask(() => {
     throw error
   })
 }
index a35887c7d3b53c48bf035d9511fb4933a6bad58e..c9baaf34ed38677b795ddef67d3e25f91d8408fb 100644 (file)
@@ -3,17 +3,17 @@ export {
   buildChargingStationAutomaticTransactionGeneratorConfiguration,
   buildConnectorsStatus,
   buildEvsesStatus,
-  OutputFormat
+  OutputFormat,
 } from './ChargingStationConfigurationUtils.js'
 export { Configuration } from './Configuration.js'
 export { Constants } from './Constants.js'
 export { ACElectricUtils, DCElectricUtils } from './ElectricUtils.js'
 export {
   handleFileException,
+  handleIncomingRequestError,
   handleSendMessageError,
   handleUncaughtException,
   handleUnhandledRejection,
-  setDefaultErrorParams
 } from './ErrorUtils.js'
 export { watchJsonFile } from './FileUtils.js'
 export { logger } from './Logger.js'
@@ -23,9 +23,9 @@ export {
   buildPerformanceStatisticsMessage,
   buildStartedMessage,
   buildStoppedMessage,
-  buildUpdatedMessage
+  buildUpdatedMessage,
 } from './MessageChannelUtils.js'
-export { max, min, nthPercentile, stdDeviation } from './StatisticUtils.js'
+export { average, max, median, min, percentile, std } from './StatisticUtils.js'
 export {
   clone,
   convertToBoolean,
@@ -40,15 +40,19 @@ export {
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
   getWebSocketCloseEventStatusString,
+  has,
   isArraySorted,
   isAsyncFunction,
+  isEmpty,
   isNotEmptyArray,
   isNotEmptyString,
   isValidDate,
   JSONStringify,
   logPrefix,
+  mergeDeepRight,
+  once,
   roundTo,
   secureRandom,
   sleep,
-  validateUUID
+  validateUUID,
 } from './Utils.js'
index f6713f8976d2cdda47d74f5951880f8d1fdfd48d..d4282e0c31141ef50c80be99b357b5f3c75129f4 100644 (file)
@@ -1,21 +1,21 @@
 import type { EventEmitterAsyncResource } from 'node:events'
-import { existsSync } from 'node:fs'
-
 import type { PoolInfo } from 'poolifier'
 
+import { existsSync } from 'node:fs'
+
 import type { SetInfo, WorkerData, WorkerOptions } from './WorkerTypes.js'
 
 export abstract class WorkerAbstract<D extends WorkerData, R extends WorkerData> {
-  protected readonly workerScript: string
-  protected readonly workerOptions: WorkerOptions
+  public abstract readonly emitter: EventEmitterAsyncResource | undefined
   public abstract readonly info: PoolInfo | SetInfo
-  public abstract readonly size: number
   public abstract readonly maxElementsPerWorker: number | undefined
-  public abstract readonly emitter: EventEmitterAsyncResource | undefined
+  public abstract readonly size: number
+
+  protected readonly workerOptions: WorkerOptions
+  protected readonly workerScript: string
 
   /**
    * `WorkerAbstract` constructor.
-   *
    * @param workerScript -
    * @param workerOptions -
    */
@@ -36,18 +36,17 @@ export abstract class WorkerAbstract<D extends WorkerData, R extends WorkerData>
     this.workerOptions = workerOptions
   }
 
+  /**
+   * Adds a task element to the worker pool/set.
+   * @param elementData -
+   */
+  public abstract addElement (elementData: D): Promise<R>
   /**
    * Starts the worker pool/set.
    */
-  public abstract start (): void | Promise<void>
+  public abstract start (): Promise<void> | void
   /**
    * Stops the worker pool/set.
    */
   public abstract stop (): Promise<void>
-  /**
-   * Adds a task element to the worker pool/set.
-   *
-   * @param elementData -
-   */
-  public abstract addElement (elementData: D): Promise<R>
 }
index ded5c89142f8e2aa9f485ed9df6ea476a7832100..0d7407dcdef8887db1fe231620408e47cf000626 100644 (file)
@@ -1,6 +1,7 @@
 import { availableParallelism } from 'poolifier'
 
 import type { WorkerOptions } from './WorkerTypes.js'
+
 import { defaultErrorHandler, defaultExitHandler } from './WorkerUtils.js'
 
 export const EMPTY_FUNCTION = Object.freeze(() => {
@@ -15,17 +16,17 @@ export const DEFAULT_POOL_MIN_SIZE = Math.floor(availableParallelism() / 2)
 export const DEFAULT_POOL_MAX_SIZE = Math.round(availableParallelism() * 1.5)
 export const DEFAULT_ELEMENTS_PER_WORKER = 1
 
-export const DEFAULT_WORKER_OPTIONS: WorkerOptions = Object.freeze({
-  workerStartDelay: DEFAULT_WORKER_START_DELAY,
+export const DEFAULT_WORKER_OPTIONS: Readonly<WorkerOptions> = Object.freeze({
   elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
-  poolMinSize: DEFAULT_POOL_MIN_SIZE,
-  poolMaxSize: DEFAULT_POOL_MAX_SIZE,
   elementsPerWorker: DEFAULT_ELEMENTS_PER_WORKER,
+  poolMaxSize: DEFAULT_POOL_MAX_SIZE,
+  poolMinSize: DEFAULT_POOL_MIN_SIZE,
   poolOptions: {
-    startWorkers: false,
     enableEvents: true,
-    restartWorkerOnError: true,
     errorHandler: defaultErrorHandler,
-    exitHandler: defaultExitHandler
-  }
+    exitHandler: defaultExitHandler,
+    restartWorkerOnError: true,
+    startWorkers: false,
+  },
+  workerStartDelay: DEFAULT_WORKER_START_DELAY,
 })
index 5217d7fe16a6772a81b946969702fe1189f0d5dc..e5808d300e5e05075639c973000d017994135293 100644 (file)
@@ -2,19 +2,35 @@ import type { EventEmitterAsyncResource } from 'node:events'
 
 import { DynamicThreadPool, type PoolInfo } from 'poolifier'
 
-import { WorkerAbstract } from './WorkerAbstract.js'
 import type { WorkerData, WorkerOptions } from './WorkerTypes.js'
+
+import { WorkerAbstract } from './WorkerAbstract.js'
 import { randomizeDelay, sleep } from './WorkerUtils.js'
 
 export class WorkerDynamicPool<D extends WorkerData, R extends WorkerData> extends WorkerAbstract<
-D,
-R
+  D,
+  R
 > {
+  get emitter (): EventEmitterAsyncResource | undefined {
+    return this.pool.emitter
+  }
+
+  get info (): PoolInfo {
+    return this.pool.info
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return undefined
+  }
+
+  get size (): number {
+    return this.pool.info.workerNodes
+  }
+
   private readonly pool: DynamicThreadPool<D, R>
 
   /**
    * Creates a new `WorkerDynamicPool`.
-   *
    * @param workerScript -
    * @param workerOptions -
    */
@@ -28,20 +44,15 @@ R
     )
   }
 
-  get info (): PoolInfo {
-    return this.pool.info
-  }
-
-  get size (): number {
-    return this.pool.info.workerNodes
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return undefined
-  }
-
-  get emitter (): EventEmitterAsyncResource | undefined {
-    return this.pool.emitter
+  /** @inheritDoc */
+  public async addElement (elementData: D): Promise<R> {
+    const response = await this.pool.execute(elementData)
+    // Start element sequentially to optimize memory at startup
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.workerOptions.elementAddDelay! > 0 &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)))
+    return response
   }
 
   /** @inheritDoc */
@@ -53,15 +64,4 @@ R
   public async stop (): Promise<void> {
     await this.pool.destroy()
   }
-
-  /** @inheritDoc */
-  public async addElement (elementData: D): Promise<R> {
-    const response = await this.pool.execute(elementData)
-    // Start element sequentially to optimize memory at startup
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.workerOptions.elementAddDelay! > 0 &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)))
-    return response
-  }
 }
index 73bcd56dc62467c7bb942157d88554014b8240af..788fa3009d11256ed1673f8b007ec040b39ecd16 100644 (file)
@@ -1,8 +1,8 @@
 import { isMainThread } from 'node:worker_threads'
 
-import { mergeDeepRight } from 'rambda'
-
 import type { WorkerAbstract } from './WorkerAbstract.js'
+
+import { mergeDeepRight } from '../utils/index.js'
 import { DEFAULT_WORKER_OPTIONS } from './WorkerConstants.js'
 import { WorkerDynamicPool } from './WorkerDynamicPool.js'
 import { WorkerFixedPool } from './WorkerFixedPool.js'
@@ -25,12 +25,12 @@ export class WorkerFactory {
     }
     workerOptions = mergeDeepRight<WorkerOptions>(DEFAULT_WORKER_OPTIONS, workerOptions ?? {})
     switch (workerProcessType) {
-      case WorkerProcessType.workerSet:
-        return new WorkerSet<D, R>(workerScript, workerOptions)
-      case WorkerProcessType.fixedPool:
-        return new WorkerFixedPool<D, R>(workerScript, workerOptions)
       case WorkerProcessType.dynamicPool:
         return new WorkerDynamicPool<D, R>(workerScript, workerOptions)
+      case WorkerProcessType.fixedPool:
+        return new WorkerFixedPool<D, R>(workerScript, workerOptions)
+      case WorkerProcessType.workerSet:
+        return new WorkerSet<D, R>(workerScript, workerOptions)
       default:
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
         throw new Error(`Worker implementation type '${workerProcessType}' not found`)
index 96060854743112a438dc408dcc8285842e1bbd19..8bddad7de428dc334efa0a2d9bd9ba61574214f8 100644 (file)
@@ -2,19 +2,35 @@ import type { EventEmitterAsyncResource } from 'node:events'
 
 import { FixedThreadPool, type PoolInfo } from 'poolifier'
 
-import { WorkerAbstract } from './WorkerAbstract.js'
 import type { WorkerData, WorkerOptions } from './WorkerTypes.js'
+
+import { WorkerAbstract } from './WorkerAbstract.js'
 import { randomizeDelay, sleep } from './WorkerUtils.js'
 
 export class WorkerFixedPool<D extends WorkerData, R extends WorkerData> extends WorkerAbstract<
-D,
-R
+  D,
+  R
 > {
+  get emitter (): EventEmitterAsyncResource | undefined {
+    return this.pool.emitter
+  }
+
+  get info (): PoolInfo {
+    return this.pool.info
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return undefined
+  }
+
+  get size (): number {
+    return this.pool.info.workerNodes
+  }
+
   private readonly pool: FixedThreadPool<D, R>
 
   /**
    * Creates a new `WorkerFixedPool`.
-   *
    * @param workerScript -
    * @param workerOptions -
    */
@@ -27,20 +43,15 @@ R
     )
   }
 
-  get info (): PoolInfo {
-    return this.pool.info
-  }
-
-  get size (): number {
-    return this.pool.info.workerNodes
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return undefined
-  }
-
-  get emitter (): EventEmitterAsyncResource | undefined {
-    return this.pool.emitter
+  /** @inheritDoc */
+  public async addElement (elementData: D): Promise<R> {
+    const response = await this.pool.execute(elementData)
+    // Start element sequentially to optimize memory at startup
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.workerOptions.elementAddDelay! > 0 &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)))
+    return response
   }
 
   /** @inheritDoc */
@@ -52,15 +63,4 @@ R
   public async stop (): Promise<void> {
     await this.pool.destroy()
   }
-
-  /** @inheritDoc */
-  public async addElement (elementData: D): Promise<R> {
-    const response = await this.pool.execute(elementData)
-    // Start element sequentially to optimize memory at startup
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.workerOptions.elementAddDelay! > 0 &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      (await sleep(randomizeDelay(this.workerOptions.elementAddDelay!)))
-    return response
-  }
 }
index c45e6a617147edce428800d8a777178eea9b53ea..babf6932487b903f594ae85a7f330e00c66ba7ba 100644 (file)
@@ -13,30 +13,54 @@ import {
   WorkerMessageEvents,
   type WorkerOptions,
   type WorkerSetElement,
-  WorkerSetEvents
+  WorkerSetEvents,
 } from './WorkerTypes.js'
 import { randomizeDelay, sleep } from './WorkerUtils.js'
 
 interface ResponseWrapper<R extends WorkerData> {
-  resolve: (value: R | PromiseLike<R>) => void
   reject: (reason?: unknown) => void
+  resolve: (value: PromiseLike<R> | R) => void
   workerSetElement: WorkerSetElement
 }
 
 export class WorkerSet<D extends WorkerData, R extends WorkerData> extends WorkerAbstract<D, R> {
   public readonly emitter: EventEmitterAsyncResource | undefined
-  private readonly workerSet: Set<WorkerSetElement>
+
+  get info (): SetInfo {
+    return {
+      elementsExecuting: [...this.workerSet].reduce(
+        (accumulator, workerSetElement) => accumulator + workerSetElement.numberOfWorkerElements,
+        0
+      ),
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      elementsPerWorker: this.maxElementsPerWorker!,
+      size: this.size,
+      started: this.started,
+      type: 'set',
+      version: workerSetVersion,
+      worker: 'thread',
+    }
+  }
+
+  get maxElementsPerWorker (): number | undefined {
+    return this.workerOptions.elementsPerWorker
+  }
+
+  get size (): number {
+    return this.workerSet.size
+  }
+
   private readonly promiseResponseMap: Map<
     `${string}-${string}-${string}-${string}`,
-  ResponseWrapper<R>
+    ResponseWrapper<R>
   >
 
   private started: boolean
+  private readonly workerSet: Set<WorkerSetElement>
   private workerStartup: boolean
 
   /**
    * Creates a new `WorkerSet`.
-   *
    * @param workerScript -
    * @param workerOptions -
    */
@@ -54,7 +78,7 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     this.workerSet = new Set<WorkerSetElement>()
     this.promiseResponseMap = new Map<
       `${string}-${string}-${string}-${string}`,
-    ResponseWrapper<R>
+      ResponseWrapper<R>
     >()
     if (this.workerOptions.poolOptions?.enableEvents === true) {
       this.emitter = new EventEmitterAsyncResource({ name: 'workerset' })
@@ -63,28 +87,33 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     this.workerStartup = false
   }
 
-  get info (): SetInfo {
-    return {
-      version: workerSetVersion,
-      type: 'set',
-      worker: 'thread',
-      started: this.started,
-      size: this.size,
-      elementsExecuting: [...this.workerSet].reduce(
-        (accumulator, workerSetElement) => accumulator + workerSetElement.numberOfWorkerElements,
-        0
-      ),
+  /** @inheritDoc */
+  public async addElement (elementData: D): Promise<R> {
+    if (!this.started) {
+      throw new Error('Cannot add a WorkerSet element: not started')
+    }
+    const workerSetElement = await this.getWorkerSetElement()
+    const sendMessageToWorker = new Promise<R>((resolve, reject) => {
+      const message = {
+        data: elementData,
+        event: WorkerMessageEvents.addWorkerElement,
+        uuid: randomUUID(),
+      } satisfies WorkerMessage<D>
+      workerSetElement.worker.postMessage(message)
+      this.promiseResponseMap.set(message.uuid, {
+        reject,
+        resolve,
+        workerSetElement,
+      })
+    })
+    const response = await sendMessageToWorker
+    // Add element sequentially to optimize memory at startup
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (this.workerOptions.elementAddDelay! > 0) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      elementsPerWorker: this.maxElementsPerWorker!
+      await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))
     }
-  }
-
-  get size (): number {
-    return this.workerSet.size
-  }
-
-  get maxElementsPerWorker (): number | undefined {
-    return this.workerOptions.elementsPerWorker
+    return response
   }
 
   /** @inheritDoc */
@@ -117,46 +146,22 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     this.emitter?.emitDestroy()
   }
 
-  /** @inheritDoc */
-  public async addElement (elementData: D): Promise<R> {
-    if (!this.started) {
-      throw new Error('Cannot add a WorkerSet element: not started')
-    }
-    const workerSetElement = await this.getWorkerSetElement()
-    const sendMessageToWorker = new Promise<R>((resolve, reject) => {
-      const message = {
-        uuid: randomUUID(),
-        event: WorkerMessageEvents.addWorkerElement,
-        data: elementData
-      } satisfies WorkerMessage<D>
-      workerSetElement.worker.postMessage(message)
-      this.promiseResponseMap.set(message.uuid, { resolve, reject, workerSetElement })
-    })
-    const response = await sendMessageToWorker
-    // Add element sequentially to optimize memory at startup
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (this.workerOptions.elementAddDelay! > 0) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      await sleep(randomizeDelay(this.workerOptions.elementAddDelay!))
-    }
-    return response
-  }
-
   /**
    * Adds a new `WorkerSetElement`.
+   * @returns The new `WorkerSetElement`.
    */
   private addWorkerSetElement (): WorkerSetElement {
     this.workerStartup = true
     const worker = new Worker(this.workerScript, {
       env: SHARE_ENV,
-      ...this.workerOptions.poolOptions?.workerOptions
+      ...this.workerOptions.poolOptions?.workerOptions,
     })
     worker.on('message', this.workerOptions.poolOptions?.messageHandler ?? EMPTY_FUNCTION)
     worker.on('message', (message: WorkerMessage<R>) => {
-      const { uuid, event, data } = message
+      const { data, event, uuid } = message
       if (this.promiseResponseMap.has(uuid)) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        const { resolve, reject, workerSetElement } = this.promiseResponseMap.get(uuid)!
+        const { reject, resolve, workerSetElement } = this.promiseResponseMap.get(uuid)!
         switch (event) {
           case WorkerMessageEvents.addedWorkerElement:
             this.emitter?.emit(WorkerSetEvents.elementAdded, this.info)
@@ -192,6 +197,7 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
         this.addWorkerSetElement()
       }
       worker.unref()
+      // eslint-disable-next-line promise/no-promise-in-callback
       worker.terminate().catch((error: unknown) => this.emitter?.emit(WorkerSetEvents.error, error))
     })
     worker.on('online', this.workerOptions.poolOptions?.onlineHandler ?? EMPTY_FUNCTION)
@@ -199,21 +205,17 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     worker.once('exit', () => {
       this.removeWorkerSetElement(this.getWorkerSetElementByWorker(worker))
     })
-    const workerSetElement: WorkerSetElement = { worker, numberOfWorkerElements: 0 }
+    const workerSetElement: WorkerSetElement = {
+      numberOfWorkerElements: 0,
+      worker,
+    }
     this.workerSet.add(workerSetElement)
     this.workerStartup = false
     return workerSetElement
   }
 
-  private removeWorkerSetElement (workerSetElement: WorkerSetElement | undefined): void {
-    if (workerSetElement == null) {
-      return
-    }
-    this.workerSet.delete(workerSetElement)
-  }
-
   private async getWorkerSetElement (): Promise<WorkerSetElement> {
-    let chosenWorkerSetElement: WorkerSetElement | undefined
+    let chosenWorkerSetElement: undefined | WorkerSetElement
     for (const workerSetElement of this.workerSet) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       if (workerSetElement.numberOfWorkerElements < this.workerOptions.elementsPerWorker!) {
@@ -232,8 +234,8 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     return chosenWorkerSetElement
   }
 
-  private getWorkerSetElementByWorker (worker: Worker): WorkerSetElement | undefined {
-    let workerSetElt: WorkerSetElement | undefined
+  private getWorkerSetElementByWorker (worker: Worker): undefined | WorkerSetElement {
+    let workerSetElt: undefined | WorkerSetElement
     for (const workerSetElement of this.workerSet) {
       if (workerSetElement.worker.threadId === worker.threadId) {
         workerSetElt = workerSetElement
@@ -242,4 +244,11 @@ export class WorkerSet<D extends WorkerData, R extends WorkerData> extends Worke
     }
     return workerSetElt
   }
+
+  private removeWorkerSetElement (workerSetElement: undefined | WorkerSetElement): void {
+    if (workerSetElement == null) {
+      return
+    }
+    this.workerSet.delete(workerSetElement)
+  }
 }
index b180983e8a9594c3e1308fa12679a4fc2cfd4493..c6b5c4737191252643c9e8524ae506f0e9b954e1 100644 (file)
@@ -2,69 +2,69 @@ import type { Worker } from 'node:worker_threads'
 
 import { type PoolEvent, PoolEvents, type ThreadPoolOptions } from 'poolifier'
 
+export enum WorkerMessageEvents {
+  addedWorkerElement = 'addedWorkerElement',
+  addWorkerElement = 'addWorkerElement',
+  workerElementError = 'workerElementError',
+}
+
 export enum WorkerProcessType {
-  workerSet = 'workerSet',
-  fixedPool = 'fixedPool',
   /** @experimental */
-  dynamicPool = 'dynamicPool'
+  dynamicPool = 'dynamicPool',
+  fixedPool = 'fixedPool',
+  workerSet = 'workerSet',
+}
+
+export enum WorkerSetEvents {
+  elementAdded = 'elementAdded',
+  elementError = 'elementError',
+  error = 'error',
+  started = 'started',
+  stopped = 'stopped',
 }
 
 export interface SetInfo {
-  version: string
-  type: string
-  worker: string
-  started: boolean
-  size: number
   elementsExecuting: number
   elementsPerWorker: number
+  size: number
+  started: boolean
+  type: string
+  version: string
+  worker: string
 }
 
-export enum WorkerSetEvents {
-  started = 'started',
-  stopped = 'stopped',
-  error = 'error',
-  elementAdded = 'elementAdded',
-  elementError = 'elementError'
+export type WorkerData = Record<string, unknown>
+
+export interface WorkerDataError extends WorkerData {
+  event: WorkerMessageEvents
+  message: string
+  name: string
+  stack?: string
 }
 
 export const WorkerEvents = {
   ...PoolEvents,
-  ...WorkerSetEvents
+  ...WorkerSetEvents,
 } as const
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 export type WorkerEvents = PoolEvent | WorkerSetEvents
 
-export interface WorkerOptions {
-  workerStartDelay?: number
+export interface WorkerMessage<T extends WorkerData> {
+  data: T
+  event: WorkerMessageEvents
+  uuid: `${string}-${string}-${string}-${string}`
+}
+
+export interface WorkerOptions extends Record<string, unknown> {
   elementAddDelay?: number
+  elementsPerWorker?: number
   poolMaxSize: number
   poolMinSize: number
-  elementsPerWorker?: number
   poolOptions?: ThreadPoolOptions
-}
-
-export type WorkerData = Record<string, unknown>
-
-export interface WorkerDataError extends WorkerData {
-  event: WorkerMessageEvents
-  name: string
-  message: string
-  stack?: string
+  workerStartDelay?: number
 }
 
 export interface WorkerSetElement {
-  worker: Worker
   numberOfWorkerElements: number
-}
-
-export interface WorkerMessage<T extends WorkerData> {
-  uuid: `${string}-${string}-${string}-${string}`
-  event: WorkerMessageEvents
-  data: T
-}
-
-export enum WorkerMessageEvents {
-  addWorkerElement = 'addWorkerElement',
-  addedWorkerElement = 'addedWorkerElement',
-  workerElementError = 'workerElementError'
+  worker: Worker
 }
index f36c6046d626d5d4850f1b75dca253edae043b15..958ecccc2f9d4a0d08e20785d7f37fbf471b726e 100644 (file)
@@ -1,6 +1,5 @@
-import { getRandomValues } from 'node:crypto'
-
 import chalk from 'chalk'
+import { getRandomValues } from 'node:crypto'
 
 export const sleep = async (milliSeconds: number): Promise<NodeJS.Timeout> => {
   return await new Promise<NodeJS.Timeout>(resolve =>
@@ -31,7 +30,6 @@ export const randomizeDelay = (delay: number): number => {
 
 /**
  * Generates a cryptographically secure random number in the [0,1[ range
- *
  * @returns A number in the [0,1[ range
  * @internal
  */
index 190a6a41034d2738ef9b5e434cec82dc29917619..32368203c86c302c24dffa1e5b41f640f3998321 100644 (file)
@@ -4,7 +4,7 @@ export {
   DEFAULT_ELEMENTS_PER_WORKER,
   DEFAULT_POOL_MAX_SIZE,
   DEFAULT_POOL_MIN_SIZE,
-  DEFAULT_WORKER_START_DELAY
+  DEFAULT_WORKER_START_DELAY,
 } from './WorkerConstants.js'
 export { WorkerFactory } from './WorkerFactory.js'
 export {
@@ -13,5 +13,5 @@ export {
   WorkerEvents,
   type WorkerMessage,
   WorkerMessageEvents,
-  WorkerProcessType
+  WorkerProcessType,
 } from './WorkerTypes.js'
diff --git a/tests/charging-station/Helpers.test.ts b/tests/charging-station/Helpers.test.ts
new file mode 100644 (file)
index 0000000..93fa649
--- /dev/null
@@ -0,0 +1,213 @@
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+
+import { expect } from '@std/expect'
+import { describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../src/charging-station/index.js'
+
+import {
+  checkChargingStationState,
+  checkConfiguration,
+  checkStationInfoConnectorStatus,
+  checkTemplate,
+  getChargingStationId,
+  getHashId,
+  getMaxNumberOfEvses,
+  getPhaseRotationValue,
+  validateStationInfo,
+} from '../../src/charging-station/Helpers.js'
+import { BaseError } from '../../src/exception/index.js'
+import {
+  type ChargingStationConfiguration,
+  type ChargingStationInfo,
+  type ChargingStationTemplate,
+  type ConnectorStatus,
+  ConnectorStatusEnum,
+  type EvseStatus,
+  OCPPVersion,
+} from '../../src/types/index.js'
+import { logger } from '../../src/utils/Logger.js'
+
+await describe('Helpers test suite', async () => {
+  const baseName = 'CS-TEST'
+  const chargingStationTemplate = {
+    baseName,
+  } as ChargingStationTemplate
+  const chargingStation = {
+    connectors: new Map<number, ConnectorStatus>(),
+    evses: new Map<number, EvseStatus>(),
+    logPrefix: () => `${baseName} |`,
+    started: false,
+  } as ChargingStation
+
+  await it('Verify getChargingStationId()', () => {
+    expect(getChargingStationId(1, chargingStationTemplate)).toBe(`${baseName}-00001`)
+  })
+
+  await it('Verify getHashId()', () => {
+    expect(getHashId(1, chargingStationTemplate)).toBe(
+      'b4b1e8ec4fca79091d99ea9a7ea5901548010e6c0e98be9296f604b9d68734444dfdae73d7d406b6124b42815214d088'
+    )
+  })
+
+  await it('Verify validateStationInfo()', () => {
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError('Missing charging station information'))
+    chargingStation.stationInfo = {} as ChargingStationInfo
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError('Missing charging station information'))
+    chargingStation.stationInfo.baseName = baseName
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
+    chargingStation.stationInfo.chargingStationId = ''
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
+    chargingStation.stationInfo.chargingStationId = getChargingStationId(1, chargingStationTemplate)
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing hashId in stationInfo properties`))
+    chargingStation.stationInfo.hashId = ''
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing hashId in stationInfo properties`))
+    chargingStation.stationInfo.hashId = getHashId(1, chargingStationTemplate)
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing templateIndex in stationInfo properties`))
+    chargingStation.stationInfo.templateIndex = 0
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new BaseError(`${baseName}-00001: Invalid templateIndex value in stationInfo properties`)
+    )
+    chargingStation.stationInfo.templateIndex = 1
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing templateName in stationInfo properties`))
+    chargingStation.stationInfo.templateName = ''
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing templateName in stationInfo properties`))
+    chargingStation.stationInfo.templateName = 'test-template.json'
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(new BaseError(`${baseName}-00001: Missing maximumPower in stationInfo properties`))
+    chargingStation.stationInfo.maximumPower = 0
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new RangeError(`${baseName}-00001: Invalid maximumPower value in stationInfo properties`)
+    )
+    chargingStation.stationInfo.maximumPower = 12000
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new BaseError(`${baseName}-00001: Missing maximumAmperage in stationInfo properties`)
+    )
+    chargingStation.stationInfo.maximumAmperage = 0
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new RangeError(`${baseName}-00001: Invalid maximumAmperage value in stationInfo properties`)
+    )
+    chargingStation.stationInfo.maximumAmperage = 16
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).not.toThrow()
+    chargingStation.stationInfo.ocppVersion = OCPPVersion.VERSION_20
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new BaseError(
+        `${baseName}-00001: OCPP 2.0 or superior requires at least one EVSE defined in the charging station template/configuration`
+      )
+    )
+    chargingStation.stationInfo.ocppVersion = OCPPVersion.VERSION_201
+    expect(() => {
+      validateStationInfo(chargingStation)
+    }).toThrow(
+      new BaseError(
+        `${baseName}-00001: OCPP 2.0 or superior requires at least one EVSE defined in the charging station template/configuration`
+      )
+    )
+  })
+
+  await it('Verify checkChargingStationState()', t => {
+    t.mock.method(logger, 'warn')
+    expect(checkChargingStationState(chargingStation, 'log prefix |')).toBe(false)
+    expect(logger.warn.mock.calls.length).toBe(1)
+    chargingStation.starting = true
+    expect(checkChargingStationState(chargingStation, 'log prefix |')).toBe(true)
+    expect(logger.warn.mock.calls.length).toBe(1)
+    chargingStation.started = true
+    expect(checkChargingStationState(chargingStation, 'log prefix |')).toBe(true)
+    expect(logger.warn.mock.calls.length).toBe(1)
+  })
+
+  await it('Verify getPhaseRotationValue()', () => {
+    expect(getPhaseRotationValue(0, 0)).toBe('0.RST')
+    expect(getPhaseRotationValue(1, 0)).toBe('1.NotApplicable')
+    expect(getPhaseRotationValue(2, 0)).toBe('2.NotApplicable')
+    expect(getPhaseRotationValue(0, 1)).toBe('0.NotApplicable')
+    expect(getPhaseRotationValue(1, 1)).toBe('1.NotApplicable')
+    expect(getPhaseRotationValue(2, 1)).toBe('2.NotApplicable')
+    expect(getPhaseRotationValue(0, 2)).toBeUndefined()
+    expect(getPhaseRotationValue(1, 2)).toBeUndefined()
+    expect(getPhaseRotationValue(2, 2)).toBeUndefined()
+    expect(getPhaseRotationValue(0, 3)).toBe('0.RST')
+    expect(getPhaseRotationValue(1, 3)).toBe('1.RST')
+    expect(getPhaseRotationValue(2, 3)).toBe('2.RST')
+  })
+
+  await it('Verify getMaxNumberOfEvses()', () => {
+    expect(getMaxNumberOfEvses(undefined)).toBe(-1)
+    expect(getMaxNumberOfEvses({})).toBe(0)
+  })
+
+  await it('Verify checkTemplate()', t => {
+    t.mock.method(logger, 'warn')
+    t.mock.method(logger, 'error')
+    expect(() => {
+      checkTemplate(undefined, 'log prefix |', 'test-template.json')
+    }).toThrow(new BaseError('Failed to read charging station template file test-template.json'))
+    expect(logger.error.mock.calls.length).toBe(1)
+    expect(() => {
+      checkTemplate({} as ChargingStationTemplate, 'log prefix |', 'test-template.json')
+    }).toThrow(
+      new BaseError('Empty charging station information from template file test-template.json')
+    )
+    expect(logger.error.mock.calls.length).toBe(2)
+    checkTemplate(chargingStationTemplate, 'log prefix |', 'test-template.json')
+    expect(logger.warn.mock.calls.length).toBe(1)
+  })
+
+  await it('Verify checkConfiguration()', t => {
+    t.mock.method(logger, 'error')
+    expect(() => {
+      checkConfiguration(undefined, 'log prefix |', 'configuration.json')
+    }).toThrow(
+      new BaseError('Failed to read charging station configuration file configuration.json')
+    )
+    expect(logger.error.mock.calls.length).toBe(1)
+    expect(() => {
+      checkConfiguration({} as ChargingStationConfiguration, 'log prefix |', 'configuration.json')
+    }).toThrow(new BaseError('Empty charging station configuration from file configuration.json'))
+    expect(logger.error.mock.calls.length).toBe(2)
+  })
+
+  await it('Verify checkStationInfoConnectorStatus()', t => {
+    t.mock.method(logger, 'warn')
+    checkStationInfoConnectorStatus(1, {} as ConnectorStatus, 'log prefix |', 'test-template.json')
+    expect(logger.warn.mock.calls.length).toBe(0)
+    const connectorStatus = {
+      status: ConnectorStatusEnum.Available,
+    } as ConnectorStatus
+    checkStationInfoConnectorStatus(1, connectorStatus, 'log prefix |', 'test-template.json')
+    expect(logger.warn.mock.calls.length).toBe(1)
+    expect(connectorStatus.status).toBeUndefined()
+  })
+})
index 947bac2bd4f0290863d364d7707d3c0d8ac2eb79..f18aff162589fa6adcfe2e6a9300a687cfe11210 100644 (file)
@@ -1,7 +1,6 @@
+import { expect } from '@std/expect'
 import { describe, it } from 'node:test'
 
-import { expect } from 'expect'
-
 import { BaseError } from '../../src/exception/BaseError.js'
 
 await describe('BaseError test suite', async () => {
diff --git a/tests/exception/OCPPError.test.ts b/tests/exception/OCPPError.test.ts
new file mode 100644 (file)
index 0000000..6ae6180
--- /dev/null
@@ -0,0 +1,22 @@
+import { expect } from '@std/expect'
+import { describe, it } from 'node:test'
+
+import { OCPPError } from '../../src/exception/OCPPError.js'
+import { ErrorType } from '../../src/types/index.js'
+import { Constants } from '../../src/utils/Constants.js'
+
+await describe('OCPPError test suite', async () => {
+  await it('Verify that OCPPError can be instantiated', () => {
+    const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, '')
+    expect(ocppError).toBeInstanceOf(OCPPError)
+    expect(ocppError.name).toBe('OCPPError')
+    expect(ocppError.message).toBe('')
+    expect(ocppError.code).toBe(ErrorType.GENERIC_ERROR)
+    expect(ocppError.command).toBe(Constants.UNKNOWN_OCPP_COMMAND)
+    expect(ocppError.details).toBeUndefined()
+    expect(typeof ocppError.stack === 'string').toBe(true)
+    expect(ocppError.stack).not.toBe('')
+    expect(ocppError.cause).toBeUndefined()
+    expect(ocppError.date).toBeInstanceOf(Date)
+  })
+})
diff --git a/tests/ocpp-server/.editorconfig b/tests/ocpp-server/.editorconfig
new file mode 100644 (file)
index 0000000..a306293
--- /dev/null
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+end_of_line = lf
+max_line_length = 100
+
+[*.py]
+indent_size = 4
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
+
+[{Makefile,**.mk}]
+# Use tabs for indentation (Makefiles require tabs)
+indent_style = tab
diff --git a/tests/ocpp-server/.gitignore b/tests/ocpp-server/.gitignore
new file mode 100644 (file)
index 0000000..5f07af3
--- /dev/null
@@ -0,0 +1,12 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+.ruff_cache/
diff --git a/tests/ocpp-server/.lintstagedrc.js b/tests/ocpp-server/.lintstagedrc.js
new file mode 100644 (file)
index 0000000..d1f9172
--- /dev/null
@@ -0,0 +1,4 @@
+export default {
+  '**/*.{json,md}': ['prettier --cache --write'],
+  '**/*.{py,pyi}': ['ruff check --fix', 'ruff format'],
+}
diff --git a/tests/ocpp-server/.vscode/extensions.json b/tests/ocpp-server/.vscode/extensions.json
new file mode 100644 (file)
index 0000000..29b973f
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  // See https://go.microsoft.com/fwlink/?LinkId=827846
+  // for the documentation about the extensions.json format
+  "recommendations": [
+    "EditorConfig.EditorConfig",
+    "streetsidesoftware.code-spell-checker",
+    "ms-python.python",
+    "charliermarsh.ruff"
+  ]
+}
diff --git a/tests/ocpp-server/.vscode/settings.json b/tests/ocpp-server/.vscode/settings.json
new file mode 100644 (file)
index 0000000..57ce569
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "editor.codeActionsOnSave": {
+    "source.fixAll": "explicit"
+  },
+  "cSpell.words": ["evse", "ocpp", "websockets"]
+}
diff --git a/tests/ocpp-server/CHANGELOG.md b/tests/ocpp-server/CHANGELOG.md
new file mode 100644 (file)
index 0000000..ee01d70
--- /dev/null
@@ -0,0 +1,152 @@
+# Changelog
+
+## [2.0.10](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.9...ocpp-server@v2.0.10) (2025-07-03)
+
+### 🧹 Chores
+
+- **ocpp-server:** Synchronize simulator-ui-ocpp-server versions
+
+## [2.0.9](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.8...ocpp-server@v2.0.9) (2025-06-27)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1442](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1442)) ([9dc2344](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9dc23444c551a9ea448544061efdc6febdca8ad9))
+
+## [2.0.8](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.7...ocpp-server@v2.0.8) (2025-05-27)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1412](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1412)) ([74d8348](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/74d8348d64f562a1c4cd74bea36955d83638949c))
+
+## [2.0.7](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.6...ocpp-server@v2.0.7) (2025-04-30)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1383](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1383)) ([4acd8d4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4acd8d4bf78b7a685421e1d8c1bf71fbd65c32ef))
+
+## [2.0.6](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.5...ocpp-server@v2.0.6) (2025-04-08)
+
+### 🧹 Chores
+
+- **ocpp-server:** Synchronize simulator-ui-ocpp-server versions
+
+## [2.0.5](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.4...ocpp-server@v2.0.5) (2025-04-08)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([3bae471](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3bae4711f84a10a63f196be92081d2644124dce0))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([3327a60](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3327a6085b4540edead4e9b90bb173e346604016))
+
+## [2.0.4](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.3...ocpp-server@v2.0.4) (2025-04-01)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([0346f5a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0346f5aeb832aca457ef033f58fcfd166e91ae28))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1356](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1356)) ([16323a6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/16323a6c2d07a70195fe4ee921fc3315b0b68d16))
+
+## [2.0.3](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.2...ocpp-server@v2.0.3) (2025-03-17)
+
+### 🐞 Bug Fixes
+
+- port OCPP 2 server code to ocpp version 2 library ([5dd0043](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5dd0043f62de284dfdfcd055d891240a696851a3))
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([1240d3f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1240d3f44065f961c318a66cd212a43774d2f3c6))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([69ef17b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/69ef17ba78fa0e4587d9a8f4ccb8e0aabd4a788b))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([b07fdee](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b07fdeec628dbee2767118c53f2f39cc718391fc))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([8d44b04](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8d44b04c638837d9661094906ee0fc762aec84e6))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([c45323e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c45323e528911f5ab21c52245f7471d4ea4d3dad))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([a00d711](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a00d7116df445153f740568d95a808a94150657f))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([70d6e16](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/70d6e160690f0cd24c37adf5fd227c9b96b26e9e))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1265](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1265)) ([c16a083](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c16a08374580fbe02b9797909b559ad18241c7a1))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1272](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1272)) ([724426b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/724426b6062a2515eaa4b96747d672cd93f4421f))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1277](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1277)) ([00c442c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/00c442c3e5e5b09307ae59cd82a9ce76483674b3))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1283](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1283)) ([4079d7d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4079d7d927671d601ebfc1d24cd3ec3010b94606))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1304](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1304)) ([a2975d2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a2975d23712aa2a609e356087963f901da8b2cc6))
+- **deps:** bump ocpp from 2.0.0rc3 to 2.0.0rc4 in /tests/ocpp-server ([#1266](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1266)) ([ef6b26b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ef6b26b096bb3c6faea89a3c62346e510d81861d))
+- **deps:** bump ocpp from 2.0.0rc4 to 2.0.0 in /tests/ocpp-server ([#1268](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1268)) ([6f05e7a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6f05e7a9c51a7a271f5a3a6867a823cf53383d5d))
+- **deps:** bump websockets from 14.2 to 15.0 in /tests/ocpp-server ([6ad66a2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6ad66a2dd098c3e767a2ff82c363355319af4725))
+- **deps:** bump websockets in /tests/ocpp-server in the regular group ([caf7b3c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/caf7b3c912da328c15e76b063448150f407a5555))
+- **deps:** bump websockets in /tests/ocpp-server in the regular group ([#1289](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1289)) ([30f283d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/30f283dd68a2262d62ad69c380b93af7a05d5672))
+
+## [2.0.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.1...ocpp-server@v2.0.2) (2024-12-23)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1236](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1236)) ([5fa6474](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5fa6474bb36abdbb4eaff8fce0946b037ae3943d))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1247](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1247)) ([7113dc0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7113dc0799591f7bb8707e7130275a01f338d126))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1252](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1252)) ([7832605](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/78326059f0d364515aab8e67297b0af8e6b27e6d))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1257](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1257)) ([8f3ff89](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8f3ff8960c98a62154a20aa4799cee85fe922817))
+- **deps-dev:** bump taskipy in /tests/ocpp-server in the regular group ([#1229](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1229)) ([957a50e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/957a50ec72f20059c9118c022fc774c6cb83b87b))
+- **deps:** bump ocpp from 2.0.0rc2 to 2.0.0rc3 in /tests/ocpp-server ([#1248](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1248)) ([45c31e7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/45c31e715047edf8eb5aeb5e0bb098902a252bf4))
+
+## [2.0.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v2.0.0...ocpp-server@v2.0.1) (2024-11-22)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([6c99ee6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6c99ee6a02a4c98147c2f47b085faef12d850a73))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1201](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1201)) ([8a80af2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8a80af287e23d31f24fde579f0db6b68405f3091))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1213](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1213)) ([89e4a23](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/89e4a238ab0be07503d933dcb62ae3688497c123))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1224](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1224)) ([c047fe5](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c047fe50eca6da3b32136bd4ce8b8a99346bc8db))
+- **deps:** bump websockets from 13.1 to 14.0 in /tests/ocpp-server ([#1214](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1214)) ([cfafab3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cfafab382b0379a6f38a215c69908917e31de434))
+- **deps:** bump websockets in /tests/ocpp-server in the regular group ([#1218](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1218)) ([9e1c610](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9e1c6101dbeaa175122a8810bc3c94521b49da61))
+
+## [2.0.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.5.2...ocpp-server@v2.0.0) (2024-10-23)
+
+### 🤖 Automation
+
+- **deps-dev:** bump taskipy in /tests/ocpp-server in the regular group ([#1199](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1199)) ([cd41213](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cd41213f50caaf842c4cc078ce5907adba68c05b))
+
+## [1.5.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.5.1...ocpp-server@v1.5.2) (2024-10-21)
+
+### 🐞 Bug Fixes
+
+- fix server task ([63bdd06](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/63bdd06f6a605d56e31dfc2787b259d190dea56c))
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff from 0.5.5 to 0.5.6 in /tests/ocpp-server ([#1121](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1121)) ([600d4c1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/600d4c16c67c6c91c97368aa59931faa86ccfc23))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([05d3347](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/05d3347e16f1a64531c1f9a8020f5196634d7062))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([5522407](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5522407ca7c53f5eb5c92f066bba27502959afc0))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1133](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1133)) ([e5ea15f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e5ea15fc2a6ca7679c9eefdb5a3f56163341ea07))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1141](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1141)) ([0f729d5](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0f729d55a33898e17bc7eae9c6789e5460e9ec29))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1142](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1142)) ([815ddf2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/815ddf2b89991ca7450c5edbb43cd34fd0c5655a))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1149](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1149)) ([e62f5d8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e62f5d862ff66ba33559c3852d63df159359a0e1))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1169](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1169)) ([a5e2da5](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a5e2da5d5e46970afadc6d4933d997830bbd7b42))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1174](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1174)) ([5e1baef](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5e1baef5574abfe01f8891acc97ad87c9e98018b))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1186](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1186)) ([2442336](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2442336b709ce2848b26f7210bb9d22cff12dc39))
+- **deps-dev:** bump ruff in /tests/ocpp-server in the regular group ([#1194](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1194)) ([8641c87](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8641c876f7bca338bfa81546298917576a7503c2))
+- **deps:** bump the regular group in /tests/ocpp-server with 2 updates ([#1154](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1154)) ([1632cc4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1632cc430da54a1ca9511f1ebe261ac6a260ac4a))
+- **deps:** bump the regular group in /tests/ocpp-server with 2 updates ([#1175](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1175)) ([455167a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/455167a9345be3811196083fbb54c3db22be62c7))
+- **deps:** bump websockets in /tests/ocpp-server in the major group ([ebea5db](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ebea5dbf6b4b042cb481ef7a7d0686c2d56ea1f4))
+
+## [1.5.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.5.0...ocpp-server@v1.5.1) (2024-07-30)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff from 0.5.4 to 0.5.5 in /tests/ocpp-server ([#1109](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1109)) ([ed9eed8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/ed9eed87bba1bee5b0a7cb06d96a5ad40a2278eb))
+
+## [1.5.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.4.2...ocpp-server@v1.5.0) (2024-07-25)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff from 0.5.2 to 0.5.3 in /tests/ocpp-server ([#1094](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1094)) ([be27d4e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/be27d4eacbbc58857c7c8a3caac51383f920b2f9))
+
+## [1.4.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.4.1...ocpp-server@v1.4.2) (2024-07-06)
+
+### 🤖 Automation
+
+- **deps-dev:** bump ruff from 0.5.0 to 0.5.1 in /tests/ocpp-server ([b6f8b09](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b6f8b09baf035ba075a837cb9199e821b2deb6fa))
+
+## [1.4.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server@v1.4.0...ocpp-server@v1.4.1) (2024-07-05)
+
+### 🧹 Chores
+
+- **ocpp-server:** Synchronize simulator-ui-ocpp-server versions
+
+## [1.4.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ocpp-server-v1.3.7...ocpp-server@v1.4.0) (2024-07-04)
+
+### 🐞 Fixes
+
+- **ocpp-server:** randomize GetBaseReport request id ([8fe113d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8fe113d7ae764df93daaa7a69c6fe810b6703587))
diff --git a/tests/ocpp-server/README.md b/tests/ocpp-server/README.md
new file mode 100644 (file)
index 0000000..ce6f31b
--- /dev/null
@@ -0,0 +1,78 @@
+# OCPP2 Mock Server
+
+This project includes an Open Charge Point Protocol (OCPP) version 2.0.1 mock server implemented in Python.
+
+## Prerequisites
+
+This project requires Python 3.7+ and [poetry](https://python-poetry.org/) to install the required packages:
+
+```shell
+poetry install --no-root
+```
+
+## Running the Server
+
+To start the server, run the `server.py` script:
+
+```shell
+poetry run task server
+```
+
+Or
+
+```shell
+poetry run python server.py
+```
+
+The server will start listening for connections on port 9000.
+
+## Running the server with OCPP command sending
+
+You can also specify a command and a period duration with the --command and --period options respectively when running the server. The server will then send your chosen command to the connected client(s) every period seconds.
+
+### GetBaseReport Command
+
+To run the server and send a GetBaseReport command every 5 seconds, use:
+
+```shell
+poetry run task server --command GetBaseReport --period 5
+```
+
+### ClearCache Command
+
+To run the server and send a ClearCache command every 5 seconds, use:
+
+```shell
+poetry run task server --command ClearCache --period 5
+```
+
+Please be mindful that these commands were examples according to the provided scenario, the available commands and their syntax might vary depending on the ocpp version and the implemented functionalities on your client.
+
+## Overview of the Server Scripts
+
+### Server.py
+
+The server script waits for connections from clients. When a client connects, the server creates a new instance of the `ChargePoint` class. This class includes methods for handling various OCPP messages, most of which return a dummy response.
+
+The server script uses the `websockets` and `ocpp` libraries to facilitate the implementation.
+
+## Development
+
+### Code formatting
+
+```shell
+poetry run task format
+```
+
+### Code linting
+
+```shell
+poetry run task lint
+```
+
+## Note
+
+Primarily, this software is intended for testing applications. The server script don't adhere to the full OCPP specifications and it is advised not to use them in a production environment without additional development.
+
+For reference:
+https://github.com/mobilityhouse/ocpp
diff --git a/tests/ocpp-server/__init__.py b/tests/ocpp-server/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/ocpp-server/poetry.lock b/tests/ocpp-server/poetry.lock
new file mode 100644 (file)
index 0000000..b9b27d3
--- /dev/null
@@ -0,0 +1,469 @@
+# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
+    {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+]
+
+[package.extras]
+benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
+tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.24.0"
+description = "An implementation of JSON Schema validation for Python"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d"},
+    {file = "jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+jsonschema-specifications = ">=2023.03.6"
+referencing = ">=0.28.4"
+rpds-py = ">=0.7.1"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.4.1"
+description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"},
+    {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"},
+]
+
+[package.dependencies]
+referencing = ">=0.31.0"
+
+[[package]]
+name = "mslex"
+version = "1.3.0"
+description = "shlex for windows"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4"},
+    {file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"},
+]
+
+[[package]]
+name = "ocpp"
+version = "2.0.0"
+description = "Python package implementing the JSON version of the Open Charge Point Protocol (OCPP)."
+optional = false
+python-versions = "<4.0,>=3.8"
+files = [
+    {file = "ocpp-2.0.0-py3-none-any.whl", hash = "sha256:45ea3f35cb0afd7a0acbc1cdf2cfd107caf371c24aca7e7a03491405bf39e626"},
+    {file = "ocpp-2.0.0.tar.gz", hash = "sha256:bbc203ae5edeb7baf43a9a24b73c6a7473179197437fb39c641f0d93afce5dc0"},
+]
+
+[package.dependencies]
+jsonschema = ">=4.4.0,<5.0.0"
+
+[[package]]
+name = "psutil"
+version = "6.1.1"
+description = "Cross-platform lib for process and system monitoring in Python."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+files = [
+    {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"},
+    {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"},
+    {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"},
+    {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"},
+    {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"},
+    {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"},
+    {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"},
+    {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"},
+    {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"},
+    {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"},
+    {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"},
+    {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"},
+    {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"},
+    {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"},
+    {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"},
+    {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"},
+    {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"},
+]
+
+[package.extras]
+dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"]
+test = ["pytest", "pytest-xdist", "setuptools"]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+description = "JSON Referencing + Python"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"},
+    {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+rpds-py = ">=0.7.0"
+typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
+
+[[package]]
+name = "rpds-py"
+version = "0.26.0"
+description = "Python bindings to Rust's persistent data structures (rpds)"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"},
+    {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc"},
+    {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19"},
+    {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11"},
+    {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f"},
+    {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323"},
+    {file = "rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45"},
+    {file = "rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84"},
+    {file = "rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed"},
+    {file = "rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d"},
+    {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3"},
+    {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107"},
+    {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a"},
+    {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318"},
+    {file = "rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a"},
+    {file = "rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03"},
+    {file = "rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41"},
+    {file = "rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d"},
+    {file = "rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a"},
+    {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323"},
+    {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158"},
+    {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3"},
+    {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2"},
+    {file = "rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44"},
+    {file = "rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c"},
+    {file = "rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8"},
+    {file = "rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d"},
+    {file = "rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04"},
+    {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1"},
+    {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9"},
+    {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9"},
+    {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba"},
+    {file = "rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b"},
+    {file = "rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5"},
+    {file = "rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9"},
+    {file = "rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9"},
+    {file = "rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a"},
+    {file = "rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246"},
+    {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387"},
+    {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af"},
+    {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33"},
+    {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953"},
+    {file = "rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9"},
+    {file = "rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37"},
+    {file = "rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f"},
+    {file = "rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7"},
+    {file = "rpds_py-0.26.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226"},
+    {file = "rpds_py-0.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915"},
+    {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284"},
+    {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21"},
+    {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292"},
+    {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d"},
+    {file = "rpds_py-0.26.0-cp39-cp39-win32.whl", hash = "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51"},
+    {file = "rpds_py-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b"},
+    {file = "rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0"},
+    {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67"},
+    {file = "rpds_py-0.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11"},
+    {file = "rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0"},
+]
+
+[[package]]
+name = "ruff"
+version = "0.12.2"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "ruff-0.12.2-py3-none-linux_armv6l.whl", hash = "sha256:093ea2b221df1d2b8e7ad92fc6ffdca40a2cb10d8564477a987b44fd4008a7be"},
+    {file = "ruff-0.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:09e4cf27cc10f96b1708100fa851e0daf21767e9709e1649175355280e0d950e"},
+    {file = "ruff-0.12.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ae64755b22f4ff85e9c52d1f82644abd0b6b6b6deedceb74bd71f35c24044cc"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb3a6b2db4d6e2c77e682f0b988d4d61aff06860158fdb413118ca133d57922"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73448de992d05517170fc37169cbca857dfeaeaa8c2b9be494d7bcb0d36c8f4b"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b94317cbc2ae4a2771af641739f933934b03555e51515e6e021c64441532d"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45fc42c3bf1d30d2008023a0a9a0cfb06bf9835b147f11fe0679f21ae86d34b1"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce48f675c394c37e958bf229fb5c1e843e20945a6d962cf3ea20b7a107dcd9f4"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793d8859445ea47591272021a81391350205a4af65a9392401f418a95dfb75c9"},
+    {file = "ruff-0.12.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6932323db80484dda89153da3d8e58164d01d6da86857c79f1961934354992da"},
+    {file = "ruff-0.12.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6aa7e623a3a11538108f61e859ebf016c4f14a7e6e4eba1980190cacb57714ce"},
+    {file = "ruff-0.12.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2a4a20aeed74671b2def096bdf2eac610c7d8ffcbf4fb0e627c06947a1d7078d"},
+    {file = "ruff-0.12.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:71a4c550195612f486c9d1f2b045a600aeba851b298c667807ae933478fcef04"},
+    {file = "ruff-0.12.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4987b8f4ceadf597c927beee65a5eaf994c6e2b631df963f86d8ad1bdea99342"},
+    {file = "ruff-0.12.2-py3-none-win32.whl", hash = "sha256:369ffb69b70cd55b6c3fc453b9492d98aed98062db9fec828cdfd069555f5f1a"},
+    {file = "ruff-0.12.2-py3-none-win_amd64.whl", hash = "sha256:dca8a3b6d6dc9810ed8f328d406516bf4d660c00caeaef36eb831cf4871b0639"},
+    {file = "ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12"},
+    {file = "ruff-0.12.2.tar.gz", hash = "sha256:d7b4f55cd6f325cb7621244f19c873c565a08aff5a4ba9c69aa7355f3f7afd3e"},
+]
+
+[[package]]
+name = "taskipy"
+version = "1.14.1"
+description = "tasks runner for python projects"
+optional = false
+python-versions = "<4.0,>=3.6"
+files = [
+    {file = "taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1"},
+    {file = "taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed"},
+]
+
+[package.dependencies]
+colorama = ">=0.4.4,<0.5.0"
+mslex = {version = ">=1.1.0,<2.0.0", markers = "sys_platform == \"win32\""}
+psutil = ">=5.7.2,<7"
+tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
+    {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
+    {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
+    {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
+    {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
+    {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
+    {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
+    {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
+    {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
+    {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
+    {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
+    {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
+    {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
+    {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
+    {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
+    {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
+    {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
+    {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
+    {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
+    {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.1"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
+    {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"},
+]
+
+[[package]]
+name = "websockets"
+version = "15.0.1"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"},
+    {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"},
+    {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"},
+    {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"},
+    {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"},
+    {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"},
+    {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"},
+    {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"},
+    {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"},
+    {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"},
+    {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"},
+    {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
+    {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
+    {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
+    {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
+    {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
+    {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"},
+    {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"},
+    {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"},
+    {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"},
+    {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"},
+    {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"},
+    {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"},
+    {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"},
+    {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"},
+    {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"},
+    {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"},
+    {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"},
+    {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"},
+    {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
+    {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
+]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.12"
+content-hash = "62614fb148de5562276e0d38d01a7ff23c60391b80b5092ef93846cb2478e4cb"
diff --git a/tests/ocpp-server/pyproject.toml b/tests/ocpp-server/pyproject.toml
new file mode 100644 (file)
index 0000000..72652f1
--- /dev/null
@@ -0,0 +1,28 @@
+[tool.poetry]
+name = "ocpp-server"
+version = "2.0.10"
+description = "OCPP2 mock server"
+authors = ["Jérôme Benoit <jerome.benoit@sap.com>"]
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.12"
+websockets = "^15.0"
+ocpp = "^2.0.0"
+
+[tool.poetry.group.dev.dependencies]
+taskipy = "^1"
+ruff = "^0.12"
+
+[tool.taskipy.tasks]
+server = "poetry run python server.py"
+format = "ruff check --fix . && ruff format ."
+lint = "ruff check --diff . && ruff format --check --diff ."
+
+[tool.ruff.lint]
+select = ["E", "W", "F", "ASYNC", "S", "B", "A", "Q", "RUF", "I"]
+
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/tests/ocpp-server/server.py b/tests/ocpp-server/server.py
new file mode 100644 (file)
index 0000000..9dce9e1
--- /dev/null
@@ -0,0 +1,248 @@
+import argparse
+import asyncio
+import logging
+from datetime import datetime, timezone
+from functools import partial
+from random import randint
+from typing import Optional
+
+import ocpp.v201
+import websockets
+from ocpp.routing import on
+from ocpp.v201.enums import (
+    Action,
+    AuthorizationStatusEnumType,
+    ClearCacheStatusEnumType,
+    GenericDeviceModelStatusEnumType,
+    RegistrationStatusEnumType,
+    ReportBaseEnumType,
+    TransactionEventEnumType,
+)
+from websockets import ConnectionClosed
+
+from timer import Timer
+
+# Setting up the logging configuration to display debug level messages.
+logging.basicConfig(level=logging.DEBUG)
+
+ChargePoints = set()
+
+
+class ChargePoint(ocpp.v201.ChargePoint):
+    _command_timer: Optional[Timer]
+
+    def __init__(self, connection):
+        super().__init__(connection.path.strip("/"), connection)
+        self._command_timer = None
+
+    # Message handlers to receive OCPP messages.
+    @on(Action.boot_notification)
+    async def on_boot_notification(self, charging_station, reason, **kwargs):
+        logging.info("Received %s", Action.boot_notification)
+        # Create and return a BootNotification response with the current time,
+        # an interval of 60 seconds, and an accepted status.
+        return ocpp.v201.call_result.BootNotification(
+            current_time=datetime.now(timezone.utc).isoformat(),
+            interval=60,
+            status=RegistrationStatusEnumType.accepted,
+        )
+
+    @on(Action.heartbeat)
+    async def on_heartbeat(self, **kwargs):
+        logging.info("Received %s", Action.heartbeat)
+        return ocpp.v201.call_result.Heartbeat(
+            current_time=datetime.now(timezone.utc).isoformat()
+        )
+
+    @on(Action.status_notification)
+    async def on_status_notification(
+        self, timestamp, evse_id: int, connector_id: int, connector_status, **kwargs
+    ):
+        logging.info("Received %s", Action.status_notification)
+        return ocpp.v201.call_result.StatusNotification()
+
+    @on(Action.authorize)
+    async def on_authorize(self, id_token, **kwargs):
+        logging.info("Received %s", Action.authorize)
+        return ocpp.v201.call_result.Authorize(
+            id_token_info={"status": AuthorizationStatusEnumType.accepted}
+        )
+
+    @on(Action.transaction_event)
+    async def on_transaction_event(
+        self,
+        event_type: TransactionEventEnumType,
+        timestamp,
+        trigger_reason,
+        seq_no: int,
+        transaction_info,
+        **kwargs,
+    ):
+        match event_type:
+            case TransactionEventEnumType.started:
+                logging.info("Received %s Started", Action.transaction_event)
+                return ocpp.v201.call_result.TransactionEvent(
+                    id_token_info={"status": AuthorizationStatusEnumType.accepted}
+                )
+            case TransactionEventEnumType.updated:
+                logging.info("Received %s Updated", Action.transaction_event)
+                return ocpp.v201.call_result.TransactionEvent(total_cost=10)
+            case TransactionEventEnumType.ended:
+                logging.info("Received %s Ended", Action.transaction_event)
+                return ocpp.v201.call_result.TransactionEvent()
+
+    @on(Action.meter_values)
+    async def on_meter_values(self, evse_id: int, meter_value, **kwargs):
+        logging.info("Received %s", Action.meter_values)
+        return ocpp.v201.call_result.MeterValues()
+
+    # Request handlers to emit OCPP messages.
+    async def _send_clear_cache(self):
+        request = ocpp.v201.call.ClearCache()
+        response = await self.call(request)
+
+        if response.status == ClearCacheStatusEnumType.accepted:
+            logging.info("%s successful", Action.clear_cache)
+        else:
+            logging.info("%s failed", Action.clear_cache)
+
+    async def _send_get_base_report(self):
+        request = ocpp.v201.call.GetBaseReport(
+            request_id=randint(1, 100),  # noqa: S311
+            report_base=ReportBaseEnumType.full_inventory,
+        )
+        response = await self.call(request)
+
+        if response.status == GenericDeviceModelStatusEnumType.accepted:
+            logging.info("%s successful", Action.get_base_report)
+        else:
+            logging.info("%s failed", Action.get_base_report)
+
+    async def _send_command(self, command_name: Action):
+        logging.debug("Sending OCPP command %s", command_name)
+        match command_name:
+            case Action.clear_cache:
+                await self._send_clear_cache()
+            case Action.get_base_report:
+                await self._send_get_base_report()
+            case _:
+                logging.info(f"Not supported command {command_name}")
+
+    async def send_command(
+        self, command_name: Action, delay: Optional[float], period: Optional[float]
+    ):
+        try:
+            if delay and not self._command_timer:
+                self._command_timer = Timer(
+                    delay,
+                    False,
+                    self._send_command,
+                    [command_name],
+                )
+            if period and not self._command_timer:
+                self._command_timer = Timer(
+                    period,
+                    True,
+                    self._send_command,
+                    [command_name],
+                )
+        except ConnectionClosed:
+            self.handle_connection_closed()
+
+    def handle_connection_closed(self):
+        logging.info("ChargePoint %s closed connection", self.id)
+        if self._command_timer:
+            self._command_timer.cancel()
+        ChargePoints.remove(self)
+        logging.debug("Connected ChargePoint(s): %d", len(ChargePoints))
+
+
+# Function to handle new WebSocket connections.
+async def on_connect(
+    websocket,
+    command_name: Optional[Action],
+    delay: Optional[float],
+    period: Optional[float],
+):
+    """For every new charge point that connects, create a ChargePoint instance and start
+    listening for messages.
+    """
+    try:
+        requested_protocols = websocket.request_headers["Sec-WebSocket-Protocol"]
+    except KeyError:
+        logging.info("Client hasn't requested any Subprotocol. Closing Connection")
+        return await websocket.close()
+
+    if websocket.subprotocol:
+        logging.info("Protocols Matched: %s", websocket.subprotocol)
+    else:
+        logging.warning(
+            "Protocols Mismatched | Expected Subprotocols: %s,"
+            " but client supports %s | Closing connection",
+            websocket.available_subprotocols,
+            requested_protocols,
+        )
+        return await websocket.close()
+
+    cp = ChargePoint(websocket)
+    if command_name:
+        await cp.send_command(command_name, delay, period)
+
+    ChargePoints.add(cp)
+
+    try:
+        await cp.start()
+    except ConnectionClosed:
+        cp.handle_connection_closed()
+
+
+def check_positive_number(value: Optional[float]):
+    try:
+        value = float(value)
+    except ValueError:
+        raise argparse.ArgumentTypeError("must be a number") from None
+    if value <= 0:
+        raise argparse.ArgumentTypeError("must be a positive number")
+    return value
+
+
+# Main function to start the WebSocket server.
+async def main():
+    parser = argparse.ArgumentParser(description="OCPP2 Server")
+    parser.add_argument("-c", "--command", type=Action, help="command name")
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument(
+        "-d",
+        "--delay",
+        type=check_positive_number,
+        help="delay in seconds",
+    )
+    group.add_argument(
+        "-p",
+        "--period",
+        type=check_positive_number,
+        help="period in seconds",
+    )
+    group.required = parser.parse_known_args()[0].command is not None
+
+    args = parser.parse_args()
+
+    # Create the WebSocket server and specify the handler for new connections.
+    server = await websockets.serve(
+        partial(
+            on_connect, command_name=args.command, delay=args.delay, period=args.period
+        ),
+        "127.0.0.1",  # Listen on loopback.
+        9000,  # Port number.
+        subprotocols=["ocpp2.0", "ocpp2.0.1"],  # Specify OCPP 2.0.1 subprotocols.
+    )
+    logging.info("WebSocket Server Started")
+
+    # Wait for the server to close (runs indefinitely).
+    await server.wait_closed()
+
+
+# Entry point of the script.
+if __name__ == "__main__":
+    # Run the main function to start the server.
+    asyncio.run(main())
diff --git a/tests/ocpp-server/timer.py b/tests/ocpp-server/timer.py
new file mode 100644 (file)
index 0000000..53e6373
--- /dev/null
@@ -0,0 +1,64 @@
+"""Timer for asyncio."""
+
+import asyncio
+
+
+class Timer:
+    def __init__(
+        self,
+        timeout: float,
+        repeat: bool,
+        callback,
+        callback_args=(),
+        callback_kwargs=None,
+    ):
+        """
+        An asynchronous Timer object.
+
+        Parameters
+        ----------
+        timeout: :class:`float`:
+        The duration for which the timer should last.
+
+        repeat: :class:`bool`:
+        Whether the timer should repeat.
+
+        callback: :class:`Coroutine` or `Method` or `Function`:
+        An `asyncio` coroutine or a regular method that will be called as soon as
+        the timer ends.
+
+        callback_args: Optional[:class:`tuple`]:
+        The args to be passed to the callback.
+
+        callback_kwargs: Optional[:class:`dict`]:
+        The kwargs to be passed to the callback.
+        """
+        self._timeout = timeout
+        self._repeat = repeat
+        self._callback = callback
+        self._task = asyncio.create_task(self._job())
+        self._callback_args = callback_args
+        if callback_kwargs is None:
+            callback_kwargs = {}
+        self._callback_kwargs = callback_kwargs
+
+    async def _job(self):
+        if self._repeat:
+            while self._task.cancelled() is False:
+                await asyncio.sleep(self._timeout)
+                await self._call_callback()
+        else:
+            await asyncio.sleep(self._timeout)
+            await self._call_callback()
+
+    async def _call_callback(self):
+        if asyncio.iscoroutine(self._callback) or asyncio.iscoroutinefunction(
+            self._callback
+        ):
+            await self._callback(*self._callback_args, **self._callback_kwargs)
+        else:
+            self._callback(*self._callback_args, **self._callback_kwargs)
+
+    def cancel(self):
+        """Cancels the timer. The callback will not be called."""
+        self._task.cancel()
diff --git a/tests/types/ConfigurationData.test.ts b/tests/types/ConfigurationData.test.ts
new file mode 100644 (file)
index 0000000..9983808
--- /dev/null
@@ -0,0 +1,28 @@
+import { expect } from '@std/expect'
+import { describe, it } from 'node:test'
+
+import {
+  ApplicationProtocolVersion,
+  ConfigurationSection,
+  SupervisionUrlDistribution,
+} from '../../src/types/ConfigurationData.js'
+
+await describe('ConfigurationData test suite', async () => {
+  await it('Verify ConfigurationSection enumeration', () => {
+    expect(ConfigurationSection.log).toBe('log')
+    expect(ConfigurationSection.performanceStorage).toBe('performanceStorage')
+    expect(ConfigurationSection.uiServer).toBe('uiServer')
+    expect(ConfigurationSection.worker).toBe('worker')
+  })
+
+  await it('Verify SupervisionUrlDistribution enumeration', () => {
+    expect(SupervisionUrlDistribution.CHARGING_STATION_AFFINITY).toBe('charging-station-affinity')
+    expect(SupervisionUrlDistribution.RANDOM).toBe('random')
+    expect(SupervisionUrlDistribution.ROUND_ROBIN).toBe('round-robin')
+  })
+
+  await it('Verify ApplicationProtocolVersion enumeration', () => {
+    expect(ApplicationProtocolVersion.VERSION_11).toBe('1.1')
+    expect(ApplicationProtocolVersion.VERSION_20).toBe('2.0')
+  })
+})
diff --git a/tests/utils/AsyncLock.test.ts b/tests/utils/AsyncLock.test.ts
new file mode 100644 (file)
index 0000000..3af1c5c
--- /dev/null
@@ -0,0 +1,44 @@
+import { expect } from '@std/expect'
+import { randomInt } from 'node:crypto'
+import { describe, it } from 'node:test'
+
+import { AsyncLock, AsyncLockType } from '../../src/utils/AsyncLock.js'
+
+await describe('AsyncLock test suite', async () => {
+  await it('Verify runExclusive() on sync fn', () => {
+    const runs = 10
+    const executed: number[] = []
+    let count = 0
+    const fn = () => {
+      executed.push(++count)
+    }
+    for (let i = 0; i < runs; i++) {
+      AsyncLock.runExclusive(AsyncLockType.configuration, fn)
+        .then(() => {
+          expect(executed).toStrictEqual(new Array(count).fill(0).map((_, i) => ++i))
+          return undefined
+        })
+        .catch(console.error)
+    }
+  })
+
+  await it('Verify runExclusive() on async fn', () => {
+    const runs = 10
+    const executed: number[] = []
+    let count = 0
+    const asyncFn = async () => {
+      await new Promise(resolve => {
+        setTimeout(resolve, randomInt(1, 100))
+      })
+      executed.push(++count)
+    }
+    for (let i = 0; i < runs; i++) {
+      AsyncLock.runExclusive(AsyncLockType.configuration, asyncFn)
+        .then(() => {
+          expect(executed).toStrictEqual(new Array(count).fill(0).map((_, i) => ++i))
+          return undefined
+        })
+        .catch(console.error)
+    }
+  })
+})
diff --git a/tests/utils/ConfigurationUtils.test.ts b/tests/utils/ConfigurationUtils.test.ts
new file mode 100644 (file)
index 0000000..f90945f
--- /dev/null
@@ -0,0 +1,22 @@
+// /* eslint-disable @typescript-eslint/no-unsafe-member-access */
+// import { expect } from '@std/expect'
+// import { describe, it } from 'node:test'
+
+// import { FileType } from '../../src/types/index.js'
+// import { handleFileException, logPrefix } from '../../src/utils/ConfigurationUtils.js'
+
+// await describe('ConfigurationUtils test suite', async () => {
+//   await it('Verify logPrefix()', () => {
+//     expect(logPrefix()).toContain(' Simulator configuration |')
+//   })
+
+//   await it('Verify handleFileException()', t => {
+//     t.mock.method(console, 'error')
+//     const error = new Error()
+//     error.code = 'ENOENT'
+//     expect(() => {
+//       handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |')
+//     }).toThrow(error)
+//     expect(console.error.mock.calls.length).toBe(1)
+//   })
+// })
index 3b025b2ee301c0baf2d8f314c7f0c66ed6b05b85..b8e47406268969d83c6def1cab0e3876d595bf94 100644 (file)
@@ -1,7 +1,6 @@
+import { expect } from '@std/expect'
 import { describe, it } from 'node:test'
 
-import { expect } from 'expect'
-
 import { ACElectricUtils, DCElectricUtils } from '../../src/utils/ElectricUtils.js'
 
 await describe('ElectricUtils test suite', async () => {
diff --git a/tests/utils/ErrorUtils.test.ts b/tests/utils/ErrorUtils.test.ts
new file mode 100644 (file)
index 0000000..9da6f63
--- /dev/null
@@ -0,0 +1,108 @@
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+
+import { expect } from '@std/expect'
+import { describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../src/charging-station/index.js'
+
+import {
+  FileType,
+  GenericStatus,
+  IncomingRequestCommand,
+  MessageType,
+  RequestCommand,
+} from '../../src/types/index.js'
+import {
+  handleFileException,
+  handleIncomingRequestError,
+  handleSendMessageError,
+} from '../../src/utils/ErrorUtils.js'
+import { logger } from '../../src/utils/Logger.js'
+
+await describe('ErrorUtils test suite', async () => {
+  const chargingStation = {
+    logPrefix: () => 'CS-TEST |',
+  } as ChargingStation
+
+  await it('Verify handleFileException()', t => {
+    t.mock.method(console, 'warn')
+    t.mock.method(console, 'error')
+    t.mock.method(logger, 'warn')
+    t.mock.method(logger, 'error')
+    const error = new Error()
+    error.code = 'ENOENT'
+    expect(() => {
+      handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {})
+    }).toThrow(error)
+    expect(() => {
+      handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
+        throwError: false,
+      })
+    }).not.toThrow()
+    expect(logger.warn.mock.calls.length).toBe(1)
+    expect(logger.error.mock.calls.length).toBe(1)
+    expect(() => {
+      handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
+        consoleOut: true,
+      })
+    }).toThrow(error)
+    expect(() => {
+      handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
+        consoleOut: true,
+        throwError: false,
+      })
+    }).not.toThrow()
+    expect(console.warn.mock.calls.length).toBe(1)
+    expect(console.error.mock.calls.length).toBe(1)
+  })
+
+  await it('Verify handleSendMessageError()', t => {
+    t.mock.method(logger, 'error')
+    t.mock.method(chargingStation, 'logPrefix')
+    const error = new Error()
+    expect(() => {
+      handleSendMessageError(
+        chargingStation,
+        RequestCommand.BOOT_NOTIFICATION,
+        MessageType.CALL_MESSAGE,
+        error
+      )
+    }).not.toThrow()
+    expect(() => {
+      handleSendMessageError(
+        chargingStation,
+        RequestCommand.BOOT_NOTIFICATION,
+        MessageType.CALL_MESSAGE,
+        error,
+        { throwError: true }
+      )
+    }).toThrow(error)
+    expect(chargingStation.logPrefix.mock.calls.length).toBe(2)
+    expect(logger.error.mock.calls.length).toBe(2)
+  })
+
+  await it('Verify handleIncomingRequestError()', t => {
+    t.mock.method(logger, 'error')
+    t.mock.method(chargingStation, 'logPrefix')
+    const error = new Error()
+    expect(() => {
+      handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error)
+    }).toThrow(error)
+    expect(() => {
+      handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error, {
+        throwError: false,
+      })
+    }).not.toThrow()
+    const errorResponse = {
+      status: GenericStatus.Rejected,
+    }
+    expect(
+      handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error, {
+        errorResponse,
+        throwError: false,
+      })
+    ).toStrictEqual(errorResponse)
+    expect(chargingStation.logPrefix.mock.calls.length).toBe(3)
+    expect(logger.error.mock.calls.length).toBe(3)
+  })
+})
index c0bf1b6cc8bf4e7a0ee3329eb1f1e73b9eb45b3e..95419fb1ddc165b2bc0a337ba760244a1a182cfc 100644 (file)
@@ -1,10 +1,23 @@
+import { expect } from '@std/expect'
 import { describe, it } from 'node:test'
 
-import { expect } from 'expect'
-
-import { max, min, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils.js'
+import { average, max, median, min, percentile, std } from '../../src/utils/StatisticUtils.js'
 
 await describe('StatisticUtils test suite', async () => {
+  await it('Verify average()', () => {
+    expect(average([])).toBe(0)
+    expect(average([0.08])).toBe(0.08)
+    expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.1642857142857146)
+    expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.8533333333333335)
+  })
+
+  await it('Verify median()', () => {
+    expect(median([])).toBe(0)
+    expect(median([0.08])).toBe(0.08)
+    expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.05)
+    expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.535)
+  })
+
   await it('Verify min()', () => {
     expect(min()).toBe(Number.POSITIVE_INFINITY)
     expect(min(0, 1)).toBe(0)
@@ -21,20 +34,20 @@ await describe('StatisticUtils test suite', async () => {
     expect(max(-1, 0)).toBe(0)
   })
 
-  await it('Verify nthPercentile()', () => {
-    expect(nthPercentile([], 25)).toBe(0)
-    expect(nthPercentile([0.08], 50)).toBe(0.08)
+  await it('Verify percentile()', () => {
+    expect(percentile([], 25)).toBe(0)
+    expect(percentile([0.08], 50)).toBe(0.08)
     const array0 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]
-    expect(nthPercentile(array0, 0)).toBe(0.25)
-    expect(nthPercentile(array0, 50)).toBe(3.05)
-    expect(nthPercentile(array0, 80)).toBe(4.974)
-    expect(nthPercentile(array0, 85)).toBe(5.131)
-    expect(nthPercentile(array0, 90)).toBe(5.434)
-    expect(nthPercentile(array0, 95)).toBe(5.736999999999999)
-    expect(nthPercentile(array0, 100)).toBe(6.04)
+    expect(percentile(array0, 0)).toBe(0.25)
+    expect(percentile(array0, 50)).toBe(3.05)
+    expect(percentile(array0, 80)).toBe(4.974)
+    expect(percentile(array0, 85)).toBe(5.131)
+    expect(percentile(array0, 90)).toBe(5.434)
+    expect(percentile(array0, 95)).toBe(5.736999999999999)
+    expect(percentile(array0, 100)).toBe(6.04)
   })
 
-  await it('Verify stdDeviation()', () => {
-    expect(stdDeviation([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.1879050645374383)
+  await it('Verify std()', () => {
+    expect(std([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.1879050645374383)
   })
 })
index 8ab1332baf76eabc8bfae51ae6354769adfc3331..42211c3a2f123ed018bbc85195bf94694f361fb9 100644 (file)
@@ -1,13 +1,14 @@
+import { expect } from '@std/expect'
+import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
+import { CircularBuffer } from 'mnemonist'
 import { randomInt } from 'node:crypto'
 import { version } from 'node:process'
 import { describe, it } from 'node:test'
-
-import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
-import { expect } from 'expect'
-import { CircularBuffer } from 'mnemonist'
 import { satisfies } from 'semver'
 
 import type { TimestampedData } from '../../src/types/index.js'
+
+import { JSRuntime, runtime } from '../../scripts/runtime.js'
 import { Constants } from '../../src/utils/Constants.js'
 import {
   clone,
@@ -20,17 +21,19 @@ import {
   formatDurationSeconds,
   generateUUID,
   getRandomFloat,
-  hasOwnProp,
+  has,
+  insertAt,
   isArraySorted,
   isAsyncFunction,
+  isEmpty,
   isNotEmptyArray,
   isNotEmptyString,
-  isObject,
   isValidDate,
+  once,
   roundTo,
   secureRandom,
   sleep,
-  validateUUID
+  validateUUID,
 } from '../../src/utils/Utils.js'
 
 await describe('Utils test suite', async () => {
@@ -205,23 +208,6 @@ await describe('Utils test suite', async () => {
     expect(extractTimeSeriesValues(circularBuffer)).toEqual([1.1, 2.2, 3.3])
   })
 
-  await it('Verify isObject()', () => {
-    expect(isObject('test')).toBe(false)
-    expect(isObject(undefined)).toBe(false)
-    expect(isObject(null)).toBe(false)
-    expect(isObject(0)).toBe(false)
-    expect(isObject([])).toBe(false)
-    expect(isObject([0, 1])).toBe(false)
-    expect(isObject(['0', '1'])).toBe(false)
-    expect(isObject({})).toBe(true)
-    expect(isObject({ 1: 1 })).toBe(true)
-    expect(isObject({ 1: '1' })).toBe(true)
-    expect(isObject(new Map())).toBe(true)
-    expect(isObject(new Set())).toBe(true)
-    expect(isObject(new WeakMap())).toBe(true)
-    expect(isObject(new WeakSet())).toBe(true)
-  })
-
   await it('Verify isAsyncFunction()', () => {
     expect(isAsyncFunction(null)).toBe(false)
     expect(isAsyncFunction(undefined)).toBe(false)
@@ -231,8 +217,7 @@ await describe('Utils test suite', async () => {
     expect(isAsyncFunction('')).toBe(false)
     expect(isAsyncFunction([])).toBe(false)
     expect(isAsyncFunction(new Date())).toBe(false)
-    // eslint-disable-next-line prefer-regex-literals
-    expect(isAsyncFunction(new RegExp('[a-z]', 'i'))).toBe(false)
+    expect(isAsyncFunction(/[a-z]/i)).toBe(false)
     expect(isAsyncFunction(new Error())).toBe(false)
     expect(isAsyncFunction(new Map())).toBe(false)
     expect(isAsyncFunction(new Set())).toBe(false)
@@ -273,17 +258,17 @@ await describe('Utils test suite', async () => {
     expect(isAsyncFunction(async function named () {})).toBe(true)
     class TestClass {
       // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public testSync (): void {}
-      // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public async testAsync (): Promise<void> {}
+      public static async testStaticAsync (): Promise<void> {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public testArrowSync = (): void => {}
+      public static testStaticSync (): void {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
       public testArrowAsync = async (): Promise<void> => {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public static testStaticSync (): void {}
+      public testArrowSync = (): void => {}
       // eslint-disable-next-line @typescript-eslint/no-empty-function
-      public static async testStaticAsync (): Promise<void> {}
+      public async testAsync (): Promise<void> {}
+      // eslint-disable-next-line @typescript-eslint/no-empty-function
+      public testSync (): void {}
     }
     const testClass = new TestClass()
     // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -314,9 +299,9 @@ await describe('Utils test suite', async () => {
     const date = new Date()
     expect(clone(date)).toStrictEqual(date)
     expect(clone(date) === date).toBe(false)
-    if (satisfies(version, '>=21.0.0')) {
+    if (runtime === JSRuntime.node && satisfies(version, '>=22.0.0')) {
       const url = new URL('https://domain.tld')
-      expect(() => clone(url)).toThrowError(new Error('Cannot clone object of unsupported type.'))
+      expect(() => clone(url)).toThrow(new Error('Cannot clone object of unsupported type.'))
     }
     const map = new Map([['1', '2']])
     expect(clone(map)).toStrictEqual(map)
@@ -325,25 +310,60 @@ await describe('Utils test suite', async () => {
     expect(clone(set)).toStrictEqual(set)
     expect(clone(set) === set).toBe(false)
     const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
-    expect(() => clone(weakMap)).toThrowError(new Error('#<WeakMap> could not be cloned.'))
+    expect(() => clone(weakMap)).toThrow(new Error('#<WeakMap> could not be cloned.'))
     const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
-    expect(() => clone(weakSet)).toThrowError(new Error('#<WeakSet> could not be cloned.'))
+    expect(() => clone(weakSet)).toThrow(new Error('#<WeakSet> could not be cloned.'))
+  })
+
+  await it('Verify once()', () => {
+    let called = 0
+    const fn = (): number => ++called
+    const onceFn = once(fn)
+    const result1 = onceFn()
+    expect(called).toBe(1)
+    expect(result1).toBe(1)
+    const result2 = onceFn()
+    expect(called).toBe(1)
+    expect(result2).toBe(1)
+    const result3 = onceFn()
+    expect(called).toBe(1)
+    expect(result3).toBe(1)
+  })
+
+  await it('Verify has()', () => {
+    expect(has('', 'test')).toBe(false)
+    expect(has('test', '')).toBe(false)
+    expect(has('test', 'test')).toBe(false)
+    expect(has('', undefined)).toBe(false)
+    expect(has('', null)).toBe(false)
+    expect(has('', [])).toBe(false)
+    expect(has('', {})).toBe(false)
+    expect(has(1, { 1: 1 })).toBe(true)
+    expect(has('1', { 1: 1 })).toBe(true)
+    expect(has(2, { 1: 1 })).toBe(false)
+    expect(has('2', { 1: 1 })).toBe(false)
+    expect(has('1', { 1: '1' })).toBe(true)
+    expect(has(1, { 1: '1' })).toBe(true)
+    expect(has('2', { 1: '1' })).toBe(false)
+    expect(has(2, { 1: '1' })).toBe(false)
   })
 
-  await it('Verify hasOwnProp()', () => {
-    expect(hasOwnProp('test', '')).toBe(false)
-    expect(hasOwnProp(undefined, '')).toBe(false)
-    expect(hasOwnProp(null, '')).toBe(false)
-    expect(hasOwnProp([], '')).toBe(false)
-    expect(hasOwnProp({}, '')).toBe(false)
-    expect(hasOwnProp({ 1: 1 }, 1)).toBe(true)
-    expect(hasOwnProp({ 1: 1 }, '1')).toBe(true)
-    expect(hasOwnProp({ 1: 1 }, 2)).toBe(false)
-    expect(hasOwnProp({ 1: 1 }, '2')).toBe(false)
-    expect(hasOwnProp({ 1: '1' }, '1')).toBe(true)
-    expect(hasOwnProp({ 1: '1' }, 1)).toBe(true)
-    expect(hasOwnProp({ 1: '1' }, '2')).toBe(false)
-    expect(hasOwnProp({ 1: '1' }, 2)).toBe(false)
+  await it('Verify isEmpty()', () => {
+    expect(isEmpty('')).toBe(true)
+    expect(isEmpty(' ')).toBe(false)
+    expect(isEmpty('     ')).toBe(false)
+    expect(isEmpty('test')).toBe(false)
+    expect(isEmpty(' test')).toBe(false)
+    expect(isEmpty('test ')).toBe(false)
+    expect(isEmpty(undefined)).toBe(false)
+    expect(isEmpty(null)).toBe(false)
+    expect(isEmpty(0)).toBe(false)
+    expect(isEmpty({})).toBe(true)
+    expect(isEmpty([])).toBe(true)
+    expect(isEmpty(new Map())).toBe(true)
+    expect(isEmpty(new Set())).toBe(true)
+    expect(isEmpty(new WeakMap())).toBe(false)
+    expect(isEmpty(new WeakSet())).toBe(false)
   })
 
   await it('Verify isNotEmptyString()', () => {
@@ -380,17 +400,15 @@ await describe('Utils test suite', async () => {
     expect(isNotEmptyArray(new WeakSet())).toBe(false)
   })
 
+  await it('Verify insertAt()', () => {
+    expect(insertAt('test', 'ing', 'test'.length)).toBe('testing')
+    // eslint-disable-next-line @cspell/spellchecker
+    expect(insertAt('test', 'ing', 2)).toBe('teingst')
+  })
+
   await it('Verify isArraySorted()', () => {
-    expect(
-      isArraySorted([], (a, b) => {
-        return a - b
-      })
-    ).toBe(true)
-    expect(
-      isArraySorted([1], (a, b) => {
-        return a - b
-      })
-    ).toBe(true)
+    expect(isArraySorted<number>([], (a, b) => a - b)).toBe(true)
+    expect(isArraySorted<number>([1], (a, b) => a - b)).toBe(true)
     expect(isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true)
     expect(isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
     expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
index fc7955a36c773740f2f5213caa2acf3280a0587a..bdded25755086915ca312901484b68070d3028ca 100644 (file)
@@ -12,7 +12,8 @@
     "verbatimModuleSyntax": true,
     "experimentalDecorators": true,
     "emitDecoratorMetadata": true,
-    "forceConsistentCasingInFileNames": true
+    "forceConsistentCasingInFileNames": true,
+    "noImplicitOverride": true
   },
-  "exclude": ["ui/web/**/*.ts"]
+  "include": ["*.ts", "src/**/*.ts", "tests/**/*.ts"]
 }
diff --git a/ui/web/.eslintrc.cjs b/ui/web/.eslintrc.cjs
deleted file mode 100644 (file)
index b6252bb..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-require('@rushstack/eslint-patch/modern-module-resolution')
-const { env } = require('node:process')
-const { defineConfig } = require('eslint-define-config')
-
-module.exports = defineConfig({
-  root: true,
-
-  env: {
-    node: true
-  },
-
-  plugins: ['simple-import-sort'],
-
-  extends: [
-    'eslint:recommended',
-    'plugin:import/recommended',
-    'plugin:import/typescript',
-    'plugin:vue/vue3-recommended',
-    '@vue/eslint-config-typescript/recommended',
-    '@vue/eslint-config-prettier'
-  ],
-
-  settings: {
-    'import/resolver': {
-      typescript: {
-        project: './tsconfig.json'
-      }
-    }
-  },
-
-  parserOptions: {
-    sourceType: 'module',
-    ecmaVersion: 'latest'
-  },
-
-  rules: {
-    'no-console': env.NODE_ENV === 'production' ? 'warn' : 'off',
-    'no-debugger': env.NODE_ENV === 'production' ? 'warn' : 'off',
-    'simple-import-sort/imports': 'error',
-    'simple-import-sort/exports': 'error',
-    'vue/multi-word-component-names': 'off'
-  }
-})
index a8ba217a05a2668399b9ff2c7782fbbd51358f59..cd4f8d434afa455df280c1d44db8d371b08cbb01 100644 (file)
@@ -1,5 +1,4 @@
 export default {
-  '*.{.css,json,md,yml,yaml,html,js,jsx,cjs,mjs,ts,tsx,cts,mts}': 'prettier --cache --write',
-  '*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}':
-    'eslint . --cache --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore'
+  '*.{css,json,md,yml,yaml,html,js,jsx,cjs,mjs,ts,tsx,cts,mts}': 'prettier --cache --write',
+  '*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}': 'eslint --cache --fix',
 }
index 066e89fcd2212b26dc027ef6abfb85510c9bae0f..b962a2dd7a7a404be05bec53d5e449f7cba2f72c 100644 (file)
@@ -4,5 +4,5 @@
   "arrowParens": "avoid",
   "singleQuote": true,
   "semi": false,
-  "trailingComma": "none"
+  "trailingComma": "es5"
 }
diff --git a/ui/web/CHANGELOG.md b/ui/web/CHANGELOG.md
new file mode 100644 (file)
index 0000000..d350874
--- /dev/null
@@ -0,0 +1,223 @@
+# Changelog
+
+## [2.0.10](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.9...webui@v2.0.10) (2025-07-03)
+
+### 🤖 Automation
+
+- **deps:** bump the regular group across 1 directory with 6 updates ([#1452](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1452)) ([b039a4f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b039a4fc9382aec82b63e48b126a47cb615f7dd4))
+
+## [2.0.9](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.8...webui@v2.0.9) (2025-06-27)
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node from 22.15.31 to 24.0.0 ([#1437](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1437)) ([b871758](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b871758ddaa7e28b479d45bf7585f172b4db36c4))
+- **deps-dev:** bump the regular group with 2 updates ([#1444](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1444)) ([dcd454c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dcd454c166a2afcc25f992d3cb7c0fb6f7a07284))
+- **deps-dev:** bump the vite group with 3 updates ([1cd81ff](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1cd81ff001b7505ddbdd9260bc5485367ac8973a))
+
+## [2.0.8](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.7...webui@v2.0.8) (2025-05-27)
+
+### 🐞 Bug Fixes
+
+- **docker:** dependencies installation with latest pnpm ([b1dab0c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b1dab0ce2c231c96a3ba1aecb559054879745170))
+
+## [2.0.7](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.6...webui@v2.0.7) (2025-04-30)
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group across 1 directory with 5 updates ([#1396](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1396)) ([637b90c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/637b90ccf2a4c187e5681ab37d394e4cf57dd5bf))
+- **deps-dev:** bump vite from 6.2.5 to 6.2.6 in the regular group ([#1382](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1382)) ([c369454](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c369454c86a86543f38ce9549d16ac57a7b4e360))
+- **deps-dev:** bump vite from 6.3.3 to 6.3.4 in the regular group ([#1400](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1400)) ([3ea8331](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3ea83318991587b84a8d7df00655f437ef170752))
+- **deps:** bump the regular group across 1 directory with 7 updates ([#1393](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1393)) ([e55d6c4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e55d6c40ca642e2dfb9f764a2b824a4eaba38494))
+
+## [2.0.6](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.5...webui@v2.0.6) (2025-04-08)
+
+### 🧹 Chores
+
+- **webui:** Synchronize simulator-ui-ocpp-server versions
+
+## [2.0.5](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.4...webui@v2.0.5) (2025-04-08)
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group with 2 updates ([1b0518d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1b0518d781c3c2f5fb08243c5e8b39cbe8da871e))
+- **deps-dev:** bump the regular group with 2 updates ([a86ad89](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a86ad89b37158d888826946197e82e28afa51606))
+
+## [2.0.4](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.3...webui@v2.0.4) (2025-04-01)
+
+### 🤖 Automation
+
+- **deps-dev:** bump the regular group with 6 updates ([da28b39](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/da28b394d059de0fd2cfa28bafe037e382411f7e))
+- **deps:** bump the regular group with 6 updates ([#1357](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1357)) ([93c3165](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/93c3165637b688b851ac9de682e9fcc8cc4a6d00))
+
+## [2.0.3](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.2...webui@v2.0.3) (2025-03-17)
+
+### 🐞 Bug Fixes
+
+- **docker:** workaround corepack bug ([88a04b7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/88a04b71ccf51cf4f94a279c95e072f0306d902b))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node in the regular group ([#1264](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1264)) ([7d3076d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7d3076df490d0c4fe2f19bba39066384bf2a6060))
+- **deps-dev:** bump @types/node in the regular group ([#1269](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1269)) ([f76119c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f76119cefac3d3ef7f8186e5e37e92c11f58be5f))
+- **deps-dev:** bump @types/node in the regular group ([#1317](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1317)) ([cb49916](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cb49916d261d2785d0fe96cdaf674e3fc6a29469))
+- **deps-dev:** bump jsdom from 25.0.1 to 26.0.0 ([#1276](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1276)) ([a082525](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a082525de20451c572433ac5cb9b4b6c0a908b1d))
+- **deps-dev:** bump the regular group across 1 directory with 6 updates ([#1300](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1300)) ([4ec2ae9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4ec2ae9d1a25c3e7605fa329a59a55b9b30708ca))
+- **deps-dev:** bump the regular group with 2 updates ([#1279](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1279)) ([84f9a57](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/84f9a5783bc76c46acf6a31c29f16f698096f318))
+- **deps-dev:** bump the regular group with 2 updates ([#1288](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1288)) ([1598cd2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1598cd2b66f5ee794b3de24c3cddb7df27e058f9))
+- **deps-dev:** bump the regular group with 3 updates ([#1292](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1292)) ([460b4f7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/460b4f787eaa9d6ec9ff928b5e674679ac069982))
+- **deps-dev:** bump the regular group with 3 updates ([#1316](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1316)) ([33f023c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/33f023c84ef127cd19d1034b0ca27dc54d18c30b))
+- **deps-dev:** bump the regular group with 5 updates ([#1347](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1347)) ([7de098e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7de098e773583a32d92bd41babf6fb7590f637ee))
+- **deps-dev:** bump typescript in the typescript group ([db6e518](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/db6e518be63b8eba2510937c9291ac34acbd9563))
+- **deps-dev:** bump typescript in the typescript group ([#1275](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1275)) ([8a16c2c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/8a16c2cd8dea56de21d7a4bfabc2178b4091f6d8))
+- **deps-dev:** bump typescript in the typescript group ([#1336](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1336)) ([f59c2bc](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f59c2bc913c580258e8e574c6faa41cfbd0e2041))
+- **deps-dev:** bump vite from 5.4.11 to 5.4.12 ([#1293](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1293)) ([3935e7d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3935e7dcb8b4d46c43f3b73db8cbd4cab0b6aa00))
+- **deps-dev:** bump vite from 5.4.14 to 6.2.0 in the vite group ([03487e6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/03487e6ed10b9dccf45836dfc6f14e8e1ad53244))
+- **deps-dev:** bump vite from 6.2.1 to 6.2.2 in the regular group ([e406114](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e406114618fc4b9055eab801a4cca08b032683d7))
+- **deps:** bump finalhandler from 1.3.1 to 2.1.0 ([4c20ab7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4c20ab72a54def59e360353ebd2d82e8c4243129))
+- **deps:** bump the regular group across 1 directory with 3 updates ([3c61d82](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/3c61d827b67cda74fa4f0ff76d169a238f0ccf06))
+- **deps:** bump the regular group across 1 directory with 3 updates ([#1285](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1285)) ([1831ac9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1831ac9e3426d88f58523de957428a121fd423b8))
+- **deps:** bump the regular group across 1 directory with 3 updates ([#1337](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1337)) ([efb264d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/efb264d54f0e3d3d0c6a024215915e95779127f8))
+- **deps:** bump the regular group across 1 directory with 5 updates ([074d58e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/074d58e7718045e8cbd1535c19f435ec27ef9d51))
+- **deps:** bump the regular group across 1 directory with 6 updates ([#1305](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1305)) ([fea949e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fea949ef7ab1ad7d8b30e34470f62703a711c067))
+- **deps:** bump the regular group across 1 directory with 9 updates ([5dd44c7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5dd44c73f3c8a77069d71631fa93e2d7ff9cbe6e))
+- **deps:** bump the regular group with 10 updates ([#1353](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1353)) ([0a7a66b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0a7a66bc2a448d881f63c361a39e193b13a8c2a3))
+- **deps:** bump the regular group with 14 updates ([97693a9](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/97693a9d2f0689847de045d4828cf3c6e1189d2e))
+- **deps:** bump the regular group with 5 updates ([dfca506](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dfca506c9ff6d5908eeb6a8941967137e3bb4574))
+- **deps:** bump the regular group with 8 updates ([fd124fb](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fd124fbe6004358e03569ae44027bb9886063d9c))
+
+## [2.0.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.1...webui@v2.0.2) (2024-12-23)
+
+### ✨ Polish
+
+- cleanup event handling callbacks ([eeb27da](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/eeb27da6a0a27a68a6d5af86c288e2bf94b81dca))
+
+### 🤖 Automation
+
+- **deps-dev:** bump prettier from 3.4.1 to 3.4.2 in the regular group ([#1243](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1243)) ([a1bbf37](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a1bbf3717246b87c33e935cdb28179eb78eae109))
+- **deps-dev:** bump the regular group with 2 updates ([#1235](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1235)) ([0414e23](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0414e23c28c3a44aea711d596516a21070a3b867))
+- **deps-dev:** bump the regular group with 5 updates ([#1239](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1239)) ([5b34f7a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/5b34f7ae2193fedb89219b108bbcc696303bd910))
+- **deps-dev:** bump the regular group with 6 updates ([#1230](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1230)) ([6ffb90e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6ffb90e3d27c1935dc65cf9517ecfec0bfe31053))
+- **deps:** bump the regular group with 2 updates ([#1228](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1228)) ([01ae0b2](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/01ae0b21e24f7490f67c7f51434c03854123be58))
+- **deps:** bump the regular group with 4 updates ([#1251](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1251)) ([2cfd250](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2cfd2508ff99ea6600088b503e592ca0ef9a114c))
+
+## [2.0.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v2.0.0...webui@v2.0.1) (2024-11-22)
+
+### 🐞 Bug Fixes
+
+- ensure local storage is cleaned at CS deletion ([122e014](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/122e014f0d4d9b9a81b6113de3d280745747670d))
+- ensure undefined is handled at computing power limitation ([55a17ee](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/55a17ee03b588b4cd5b14276b1f103bcdd3ec0a4)), closes [#1223](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1223)
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node in the regular group ([05eeba4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/05eeba4c543c89a098f89d2859922ecca92d81d3))
+- **deps-dev:** bump @types/node in the regular group ([#1206](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1206)) ([009b39e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/009b39e67fc3ed602b676daa8d7984fee62a45dc))
+- **deps-dev:** bump @types/node in the regular group ([#1207](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1207)) ([a8ecc38](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a8ecc3853c85790d59a67befa2d31e588624d29d))
+- **deps-dev:** bump the regular group with 4 updates ([#1217](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1217)) ([639c849](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/639c849b2cf9fbc586f7b101896f7ed0cf61066e))
+- **deps:** bump the regular group across 1 directory with 2 updates ([#1203](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1203)) ([90eb3b3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/90eb3b34a3679829df20d3b008115d23444d1c1e))
+- **deps:** bump the regular group with 11 updates ([#1215](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1215)) ([1c3e200](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1c3e200fa957bae57aa33da1117828abd6d7b61e))
+- **deps:** bump the regular group with 2 updates ([#1212](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1212)) ([7c4f2ba](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/7c4f2baeb2988e79e98a77a969f800c267c1fb44))
+- **deps:** bump the regular group with 2 updates ([#1219](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1219)) ([9b823a0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9b823a0faa557dadeb5ea951e76fb40ce875b553))
+- **deps:** bump the regular group with 5 updates ([#1205](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1205)) ([6e0ee99](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6e0ee990fee626e27d99103aad81ccbc9658fd63))
+- **deps:** bump the regular group with 6 updates ([#1211](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1211)) ([636421d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/636421d3912f65978bc31ab486d4d9f0f6e3cc3d))
+- fix linter errors ([408b4e6](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/408b4e6d9b9a617ef4a1fc465e407e4711886b24))
+
+## [2.0.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v1.5.2...webui@v2.0.0) (2024-10-23)
+
+### 🤖 Automation
+
+- **deps:** bump the regular group with 2 updates ([#1197](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1197)) ([6f31a7a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6f31a7a48fbe760691ebc36695ca8ab07b538f8b))
+
+## [1.5.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v1.5.1...webui@v1.5.2) (2024-10-21)
+
+### 🐞 Bug Fixes
+
+- silence TS strict null check errors ([df60841](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/df60841b5f33a375830f6c8865bedaa9a54da65e))
+
+### ✨ Polish
+
+- cleanup blank lines ([b1421bc](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b1421bc351387bb5efd738f0be8cfd5ab94a4426))
+- **deps-dev:** remove unused deps ([fd855c1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fd855c1b97eb0eb50caba0f766c94b2cea4ff5e0))
+- display transaction id in UI ([99fb669](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/99fb669336a4182fa6932a1e1b1c0eb2fad834aa))
+- **docker:** rename start.sh to run.sh ([baf8b16](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/baf8b16408ca3209ec4b9b8b07e1a9b026761444))
+- separate out dashboard docker image ([20fb109](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/20fb10949dc2553d7695be1aab02bf84a8ddab98)), closes [#1040](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1040)
+- switch to eslint-plugin-perfectionist ([0749233](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0749233f25516e4c73ee8dbcea8c4ad6b8a506bb))
+- turn on `noImplicitOverride` in TS configuration ([6375d3c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/6375d3cdd7f42a6e125976df194f4fe689d24113))
+- use ENTRYPOINT syntax in docker files ([989108f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/989108f63bd531f04b28e65c8b499c9dbf5bbed0))
+
+### 🧪 Tests
+
+- add checkStationInfoConnectorStatus() test ([dd81d27](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/dd81d27d043073971f29ec761261489f16c66541))
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node from 22.0.3 to 22.1.0 ([#1120](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1120)) ([c11dc2a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c11dc2a0aca044ee03dc565dfaff7342b4ce039a))
+- **deps-dev:** bump @vitest/coverage-v8 from 2.0.4 to 2.0.5 ([#1117](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1117)) ([4ee8259](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4ee82592a066961b6877858ed29a825bfd480acf))
+- **deps-dev:** bump jsdom from 24.1.1 to 25.0.0 in the major group ([#1152](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1152)) ([bb7aeb1](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bb7aeb1e64f53b1b3af1b1b33c405bb13310578c))
+- **deps-dev:** bump the regular group with 2 updates ([cc3e26b](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/cc3e26b41348dffd62937a9b44e4b22d963beebc))
+- **deps-dev:** bump the regular group with 2 updates ([#1156](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1156)) ([d366869](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d36686932f284482ecfd095387a717b92ce64b5a))
+- **deps-dev:** bump the regular group with 2 updates ([#1183](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1183)) ([9de6af7](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/9de6af7490dc616d685c474b0d54c19c53fb15cd))
+- **deps-dev:** bump the regular group with 3 updates ([#1167](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1167)) ([fcca987](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fcca98745121549db537d9ce588e38900c8f0e0d))
+- **deps-dev:** bump vitest from 2.0.4 to 2.0.5 ([#1116](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1116)) ([e454b55](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e454b5538856316f8492463262186a24e02e2925))
+- **deps:** bump the regular group across 1 directory with 12 updates ([#1140](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1140)) ([89e8682](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/89e8682d8e1b3936bd17af7eca0142f9ad695b7f))
+- **deps:** bump the regular group across 1 directory with 4 updates ([58e990c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/58e990c7169a44eaa5ee14029119713ce92f5033))
+- **deps:** bump the regular group with 10 updates ([#1153](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1153)) ([48bd33f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/48bd33f9432231b9551d65ceb5cd14b937e5cff1))
+- **deps:** bump the regular group with 2 updates ([#1129](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1129)) ([d9f951c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d9f951c258d12765106548688672b8413716fb16))
+- **deps:** bump the regular group with 2 updates ([#1143](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1143)) ([f883a83](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f883a83f647b54a7b625e19972e94224f768c50b))
+- **deps:** bump the regular group with 2 updates ([#1155](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1155)) ([c02ea07](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c02ea0790b61c5b29c353ff1297fa5e3fe5538da))
+- **deps:** bump the regular group with 2 updates ([#1165](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1165)) ([e1fb067](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e1fb067c6a2d00106c5bf7ec0707d5374fdca464))
+- **deps:** bump the regular group with 2 updates ([#1189](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1189)) ([d626254](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d626254db881732a6478258428fd37da26c4cabb))
+- **deps:** bump the regular group with 3 updates ([#1151](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1151)) ([271426f](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/271426fb72220de7969c8200067f51793a5502a7))
+- **deps:** bump the regular group with 3 updates ([#1170](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1170)) ([fb89e15](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fb89e155dbfd8be508fa42a08fc44dad925bd7ad))
+- **deps:** bump the regular group with 4 updates ([#1157](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1157)) ([35fbcd3](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/35fbcd383c244ae746b45c031abac4b6cbadf001))
+- **deps:** bump the regular group with 4 updates ([#1166](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1166)) ([2f2d625](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2f2d625b102d7211e5311c8367690bd03ce8087a))
+- **deps:** bump the regular group with 4 updates ([#1173](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1173)) ([b07a57e](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b07a57e8de27faf8008b26e94f78978cc1b58ff4))
+- **deps:** bump the regular group with 5 updates ([#1191](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1191)) ([c9a4d44](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/c9a4d44f8a45e92a7b5dd755c0b811c85f5c2f6e))
+- **deps:** bump the regular group with 6 updates ([#1163](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1163)) ([e6ea62c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e6ea62c8b4c1ba8c99c8502a9df24709f46e19cf))
+- **deps:** bump the regular group with 7 updates ([f5d5fe0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f5d5fe0e8449c8c1ecd560cdaea66757d4cb6168))
+- **deps:** bump the regular group with 7 updates ([#1130](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1130)) ([4f1d887](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4f1d8877c57dd59ec6b862c5caee613532e0873a))
+- **deps:** bump the regular group with 7 updates ([#1158](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1158)) ([4809da4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/4809da42ba62306e05652e685aba604bc66ec179))
+- **deps:** bump the regular group with 7 updates ([#1168](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1168)) ([2336b18](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/2336b18a25e03b086d23fd7c99753ac9201eee6e))
+- **deps:** bump the regular group with 9 updates ([#1137](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1137)) ([eede99d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/eede99da5f4df39204a48daf6caf8463c1048be6))
+- **deps:** bump vue from 3.4.36 to 3.4.37 in the regular group ([#1132](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1132)) ([1e755e0](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/1e755e07ca8925d906b4b83a9a430d4d4dc4b31d))
+- **deps:** bump vue from 3.5.1 to 3.5.2 in the regular group ([54e7c79](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/54e7c7947381865de499ddff816d007399942040))
+- **deps:** bump vue from 3.5.10 to 3.5.11 in the regular group ([#1184](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1184)) ([b216885](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/b216885bc638297def6a91f530813a4ab078dc5c))
+- **docker:** fix dashboard auto config paths ([abba1ad](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/abba1ad8aec06b4c1783adc6893c09ce108f08a3))
+- **sonar:** refine sonar-project.properties files ([216a56a](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/216a56a2a3c4548eb9fdb3a67793e653b4f8bbf3))
+
+## [1.5.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v1.5.0...webui@v1.5.1) (2024-07-30)
+
+### 🧹 Chores
+
+- **webui:** Synchronize simulator-ui-ocpp-server versions
+
+## [1.5.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui@v1.4.2...webui@v1.5.0) (2024-07-25)
+
+### 🤖 Automation
+
+- **deps-dev:** bump @types/node from 20.14.11 to 20.14.12 ([#1106](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1106)) ([fb46a87](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/fb46a877f5c2b880759b9e70a08092510a7cb473))
+- **deps-dev:** bump @vitejs/plugin-vue from 5.0.5 to 5.1.0 ([#1105](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1105)) ([d36c4c8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/d36c4c840ab5ac7a9c7fe6abbd2ae01844cb981e))
+- **deps-dev:** bump @vitest/coverage-v8 from 2.0.1 to 2.0.2 ([bbdb386](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/bbdb3861ed14ea52669231e5d372add76c50d65e))
+- **deps-dev:** bump rimraf from 5.0.8 to 6.0.0 ([207408c](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/207408cf661a7b8a1d35f8a9aa5ff02104fcd8ba))
+- **deps-dev:** bump typescript-eslint from 7.16.0 to 7.16.1 ([a60a99d](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a60a99dce05328466e9a631ba83a9d527f3b4548))
+- **deps-dev:** bump vitest from 2.0.1 to 2.0.2 ([#1082](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1082)) ([f3d0d30](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f3d0d30346974ec0e979d53d8a761ce254ba8bb0))
+- **deps:** bump vue from 3.4.33 to 3.4.34 ([#1107](https://github.com/SAP/e-mobility-charging-stations-simulator/issues/1107)) ([0bfef14](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/0bfef1423a926bd6054a25934957148d1d2fb4b0))
+
+## [1.4.2](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/webui-v1.4.1...webui@v1.4.2) (2024-07-06)
+
+### 🧹 Chores
+
+- **webui:** Synchronize simulator-ui-ocpp-server versions
+
+## [1.4.1](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ui@v1.4.0...ui@v1.4.1) (2024-07-05)
+
+### 🐞 Fixes
+
+- **ci:** fix release branches creation ([f727f02](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/f727f029cb974cffb38a2c569173730b6b808e3f))
+
+## [1.4.0](https://github.com/SAP/e-mobility-charging-stations-simulator/compare/ui-v1.3.7...ui@v1.4.0) (2024-07-04)
+
+### 🤖 Automation
+
+- bump volta node version ([abc5de8](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/abc5de857ea9e4a27f83d1c2fe60b4618775a540))
+- **deps-dev:** apply updates ([e92bd99](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/e92bd991d1f0c43442dd20d7b5e34fd30f9bb136))
+- **deps-dev:** apply updates ([a33e3b4](https://github.com/SAP/e-mobility-charging-stations-simulator/commit/a33e3b4129757990b84af075cf9b678506937afb))
index 43bb9569139480ec1b1d3fd25f130666e4ec01c5..039d90156c4c7c6ef14cdf124bcd5bb3558e1cc4 100644 (file)
@@ -17,6 +17,7 @@ The Web UI code and configuration is in the repository directory [ui/web](./../.
   - [Run](#run)
     - [Compiles and run for production](#compiles-and-run-for-production)
     - [Preview locally](#preview-locally)
+    - [Docker](#docker)
   - [Development](#development)
     - [Compiles and run for development](#compiles-and-run-for-development)
     - [Formats files](#formats-files)
@@ -119,6 +120,14 @@ You can now follow the link displayed in the terminal. The Web UI looks like the
 1. With the buttons on the top you can change UI server, start/stop the simulator, add new charging stations and refresh the content.
 2. Each charging station is a row in the table with specific 'Actions' to execute on. Try 'Stop Charging Station' and refresh with the large blue button and see the status 'Started' turns from 'Yes' into 'No'.
 
+#### Docker
+
+In the [docker](./docker) folder:
+
+```shell
+make
+```
+
 ### Development
 
 #### Compiles and run for development
diff --git a/ui/web/docker/Dockerfile b/ui/web/docker/Dockerfile
new file mode 100644 (file)
index 0000000..8b25032
--- /dev/null
@@ -0,0 +1,26 @@
+FROM node:lts-alpine AS builder
+
+# Build dashboard
+WORKDIR /usr/builder
+COPY . ./
+RUN set -ex \
+  && npm install -g --ignore-scripts corepack \
+  && corepack enable \
+  && corepack prepare pnpm@latest --activate \
+  && pnpm set progress=false \
+  && pnpm config set depth 0 \
+  && pnpm install --ignore-scripts \
+  && cp docker/config.json public/config.json \
+  && pnpm build
+
+FROM node:lts-alpine
+
+WORKDIR /usr/app
+COPY --from=builder /usr/builder ./
+COPY docker/run.sh /run.sh
+COPY docker/autoconfig.sh /autoconfig.sh
+RUN set -ex \
+  && chmod +x /run.sh \
+  && chmod +x /autoconfig.sh
+
+ENTRYPOINT ["/bin/sh", "-c", "/autoconfig.sh && /run.sh"]
diff --git a/ui/web/docker/Makefile b/ui/web/docker/Makefile
new file mode 100644 (file)
index 0000000..fc0e9cd
--- /dev/null
@@ -0,0 +1,50 @@
+PROJECT_NAME?=evse
+NAME:=e-mobility-charging-stations-dashboard
+SUBMODULES_INIT?=false
+DOCKER_ECR_ACCOUNT_ID?=166296450311
+DOCKER_ECR_REGION?=eu-west-3
+DOCKER_ECR_REGISTRY_NAME?=e-mobility-charging-stations-dashboard
+DOCKER_ECR_TAG?=latest
+
+.PHONY: all
+
+default: all
+
+submodule-update:
+       git submodule update --init --recursive
+
+submodules-init=
+ifeq '$(SUBMODULES_INIT)' 'true'
+       submodules-init += submodule-update
+endif
+
+$(NAME): $(submodules-init)
+       docker compose -p $(PROJECT_NAME) up -d
+
+$(NAME)-force: $(submodules-init)
+       docker compose -p $(PROJECT_NAME) up -d --build --force-recreate
+
+all: $(NAME)
+
+clean-images:
+       -docker rmi $(PROJECT_NAME)-$(NAME)
+
+clean-containers:
+       -docker compose -p $(PROJECT_NAME) down
+
+clean: clean-containers clean-images
+
+docker-tag-ecr:
+       docker tag $(PROJECT_NAME)-$(NAME) $(DOCKER_ECR_ACCOUNT_ID).dkr.ecr.$(DOCKER_ECR_REGION).amazonaws.com/$(DOCKER_ECR_REGISTRY_NAME):$(DOCKER_ECR_TAG)
+
+docker-push-ecr: $(NAME)-force docker-tag-ecr
+       aws ecr get-login-password --region $(DOCKER_ECR_REGION) | docker login --username AWS --password-stdin $(DOCKER_ECR_ACCOUNT_ID).dkr.ecr.$(DOCKER_ECR_REGION).amazonaws.com/$(DOCKER_ECR_REGISTRY_NAME)
+       docker push $(DOCKER_ECR_ACCOUNT_ID).dkr.ecr.$(DOCKER_ECR_REGION).amazonaws.com/$(DOCKER_ECR_REGISTRY_NAME):$(DOCKER_ECR_TAG)
+
+dist-clean-images:
+       docker image prune -a -f
+
+dist-clean-volumes:
+       docker volume prune -f
+
+dist-clean: clean-containers dist-clean-volumes dist-clean-images
diff --git a/ui/web/docker/autoconfig.sh b/ui/web/docker/autoconfig.sh
new file mode 100755 (executable)
index 0000000..95e6e3e
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+
+if ! [ -z $emobility_server_type ] && ! [ -z $emobility_service_type ]
+then
+  [ -z $emobility_install_dir ] && { echo "emobility installation directory env variable not found, exiting"; exit 1; }
+  [ -z $emobility_landscape ] && { echo "emobility landscape env variable not found, exiting"; exit 1; }
+  [ -z $emobility_server_type ] && { echo "emobility env server type variable not found, exiting"; exit 1; }
+  [ -z $emobility_service_type ] && { echo "emobility env service type variable not found, exiting"; exit 1; }
+
+  cp $emobility_install_dir/src/assets/configs-docker/$emobility_server_type-$emobility_service_type-$emobility_landscape.json $emobility_install_dir/dist/config.json
+else
+  echo "no emobility env defined, start with default configuration"
+fi
diff --git a/ui/web/docker/config.json b/ui/web/docker/config.json
new file mode 100644 (file)
index 0000000..1542583
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "uiServer": {
+    "host": "localhost",
+    "port": 8080,
+    "protocol": "ui",
+    "version": "0.0.1",
+    "authentication": {
+      "enabled": true,
+      "type": "protocol-basic-auth",
+      "username": "admin",
+      "password": "admin"
+    }
+  }
+}
diff --git a/ui/web/docker/docker-compose.yml b/ui/web/docker/docker-compose.yml
new file mode 100644 (file)
index 0000000..0220dfe
--- /dev/null
@@ -0,0 +1,12 @@
+networks:
+  ev_network:
+    driver: bridge
+services:
+  e-mobility-charging-stations-dashboard:
+    build:
+      context: ..
+      dockerfile: docker/Dockerfile
+    networks:
+      - ev_network
+    ports:
+      - 3030:3030
diff --git a/ui/web/docker/run.sh b/ui/web/docker/run.sh
new file mode 100755 (executable)
index 0000000..865f7dd
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+node start.js
index ecc852416e2d419a0237bac30878b5b78d4d95c9..5982b5add989a99478aecc8b13b41d4c428d8c96 100644 (file)
@@ -1,17 +1,17 @@
 {
   "$schema": "https://json.schemastore.org/package",
   "name": "webui",
-  "version": "0.3.0",
+  "version": "2.0.10",
   "readme": "README.md",
   "engines": {
-    "node": ">=18.0.0",
+    "node": ">=20.0.0",
     "pnpm": ">=9.0.0"
   },
   "volta": {
-    "node": "22.2.0",
-    "pnpm": "9.1.2"
+    "node": "22.17.0",
+    "pnpm": "10.12.4"
   },
-  "packageManager": "pnpm@9.1.2",
+  "packageManager": "pnpm@10.12.4",
   "type": "module",
   "scripts": {
     "build": "vite build",
     "dev": "vite",
     "clean:dist": "pnpm exec rimraf dist",
     "clean:node_modules": "pnpm exec rimraf node_modules",
-    "lint": "cross-env TIMING=1 eslint . --cache --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
-    "lint:fix": "cross-env TIMING=1 eslint . --cache --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+    "lint": "cross-env TIMING=1 eslint --cache .",
+    "lint:fix": "cross-env TIMING=1 eslint --cache --fix .",
     "format": "prettier --cache --write .",
     "test": "vitest",
     "coverage": "vitest run --coverage"
   },
   "dependencies": {
-    "finalhandler": "^1.2.0",
-    "serve-static": "^1.15.0",
-    "vue": "^3.4.27",
-    "vue-router": "^4.3.2",
-    "vue-toast-notification": "^3.1.2"
+    "finalhandler": "^2.1.0",
+    "serve-static": "^2.2.0",
+    "vue": "^3.5.17",
+    "vue-router": "^4.5.1",
+    "vue-toast-notification": "^3.1.3"
   },
   "devDependencies": {
-    "@rushstack/eslint-patch": "^1.10.3",
-    "@tsconfig/node20": "^20.1.4",
-    "@types/jsdom": "^21.1.6",
-    "@types/node": "^20.12.12",
-    "@typescript-eslint/eslint-plugin": "^7.10.0",
-    "@typescript-eslint/parser": "^7.10.0",
-    "@vitejs/plugin-vue": "^5.0.4",
-    "@vitejs/plugin-vue-jsx": "^3.1.0",
-    "@vitest/coverage-v8": "^1.6.0",
-    "@vue/eslint-config-prettier": "^9.0.0",
-    "@vue/eslint-config-typescript": "^13.0.0",
+    "@tsconfig/node22": "^22.0.2",
+    "@types/jsdom": "^21.1.7",
+    "@types/node": "^24.0.10",
+    "@vitejs/plugin-vue": "^6.0.0",
+    "@vitejs/plugin-vue-jsx": "^5.0.1",
+    "@vitest/coverage-v8": "^3.2.4",
     "@vue/test-utils": "^2.4.6",
-    "@vue/tsconfig": "^0.5.1",
+    "@vue/tsconfig": "^0.7.0",
     "cross-env": "^7.0.3",
-    "eslint": "^8.57.0",
-    "eslint-define-config": "^2.1.0",
-    "eslint-import-resolver-typescript": "^3.6.1",
-    "eslint-plugin-import": "^2.29.1",
-    "eslint-plugin-simple-import-sort": "^12.1.0",
-    "eslint-plugin-vue": "^9.26.0",
-    "jsdom": "^24.0.0",
-    "prettier": "^3.2.5",
-    "rimraf": "^5.0.7",
-    "typescript": "~5.4.5",
-    "vite": "^5.2.11",
-    "vitest": "^1.6.0"
-  },
-  "_id": "webui@0.3.0"
+    "jsdom": "^26.1.0",
+    "prettier": "^3.6.2",
+    "rimraf": "^6.0.1",
+    "typescript": "~5.8.3",
+    "vite": "^7.0.2",
+    "vitest": "^3.2.4"
+  }
 }
index 77ba547c10079e20c78068544f32c26cf6e3346a..34fc52457787de6f4b5f7aadbff59df0efd07d04 100644 (file)
@@ -3,13 +3,15 @@ sonar.organization=sap-1
 
 # This is the name and version displayed in the SonarCloud UI.
 sonar.projectName=e-mobility-charging-stations-simulator-webui
-sonar.projectVersion=0.3.0
+# x-release-please-start-version
+sonar.projectVersion=2.0.10
+# x-release-please-end
 
 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
 sonar.sources=src
 sonar.tests=tests
 
-sonar.javascript.lcov.reportPaths=coverage/lcov.info
+sonar.typescript.lcov.reportPaths=coverage/lcov.info
 
 # Encoding of the source code. Default is default system encoding
 #sonar.sourceEncoding=UTF-8
index 451a7659d45e73807f69d596d417db6f0156fd28..0bc72b01a60da5457c18a981daa8517063469cbc 100644 (file)
@@ -1,6 +1,6 @@
 <template>
   <div class="container">
-    <slot></slot>
+    <slot />
   </div>
 </template>
 
index a95cba2b07a9afdf72cf03a2be97b81def9db712..ae2dc602105ca276ea132ac385622b565f815124 100644 (file)
@@ -1,11 +1,21 @@
 <template>
-  <h1 id="action">Add Charging Stations</h1>
+  <h1 id="action">
+    Add Charging Stations
+  </h1>
   <p>Template:</p>
-  <select :key="state.renderTemplates" v-model="state.template">
-    <option disabled value="">Please select a template</option>
+  <select
+    :key="state.renderTemplates"
+    v-model="state.template"
+  >
+    <option
+      disabled
+      value=""
+    >
+      Please select a template
+    </option>
     <option
-      v-for="template in $templates.value"
-      v-show="Array.isArray($templates.value) && $templates.value.length > 0"
+      v-for="template in $templates!.value"
+      v-show="Array.isArray($templates?.value) && $templates.value.length > 0"
       :key="template"
     >
       {{ template }}
   <input
     id="number-of-stations"
     v-model="state.numberOfStations"
-    type="number"
     min="1"
     name="number-of-stations"
     placeholder="number of stations"
-  />
+    type="number"
+  >
   <p>Template options overrides:</p>
   <ul id="template-options">
     <li>
       <input
         id="supervision-url"
         v-model.trim="state.supervisionUrl"
-        type="url"
         name="supervision-url"
         placeholder="wss://"
-      />
+        type="url"
+      >
     </li>
     <li>
       Auto start:
-      <input v-model="state.autoStart" type="checkbox" true-value="true" false-value="false" />
+      <input
+        v-model="state.autoStart"
+        false-value="false"
+        true-value="true"
+        type="checkbox"
+      >
     </li>
     <li>
       Persistent configuration:
       <input
         v-model="state.persistentConfiguration"
-        type="checkbox"
-        true-value="true"
         false-value="false"
-      />
+        true-value="true"
+        type="checkbox"
+      >
     </li>
     <li>
       OCPP strict compliance:
       <input
         v-model="state.ocppStrictCompliance"
-        type="checkbox"
-        true-value="true"
         false-value="false"
-      />
+        true-value="true"
+        type="checkbox"
+      >
     </li>
     <li>
       Performance statistics:
       <input
         v-model="state.enableStatistics"
-        type="checkbox"
-        true-value="true"
         false-value="false"
-      />
+        true-value="true"
+        type="checkbox"
+      >
     </li>
   </ul>
-  <br />
+  <br>
   <Button
     id="action-button"
     @click="
       () => {
         $uiClient
-          .addChargingStations(state.template, state.numberOfStations, {
+          ?.addChargingStations(state.template, state.numberOfStations, {
             supervisionUrls: state.supervisionUrl.length > 0 ? state.supervisionUrl : undefined,
             autoStart: convertToBoolean(state.autoStart),
             persistentConfiguration: convertToBoolean(state.persistentConfiguration),
             ocppStrictCompliance: convertToBoolean(state.ocppStrictCompliance),
-            enableStatistics: convertToBoolean(state.enableStatistics)
+            enableStatistics: convertToBoolean(state.enableStatistics),
           })
           .then(() => {
             $toast.success('Charging stations successfully added')
@@ -101,26 +116,26 @@ import Button from '@/components/buttons/Button.vue'
 import { convertToBoolean, randomUUID } from '@/composables'
 
 const state = ref<{
-  renderTemplates: `${string}-${string}-${string}-${string}-${string}`
-  template: string
-  numberOfStations: number
-  supervisionUrl: string
   autoStart: boolean
-  persistentConfiguration: boolean
-  ocppStrictCompliance: boolean
   enableStatistics: boolean
+  numberOfStations: number
+  ocppStrictCompliance: boolean
+  persistentConfiguration: boolean
+  renderTemplates: `${string}-${string}-${string}-${string}-${string}`
+  supervisionUrl: string
+  template: string
 }>({
-  renderTemplates: randomUUID(),
-  template: '',
-  numberOfStations: 1,
-  supervisionUrl: '',
   autoStart: false,
-  persistentConfiguration: true,
+  enableStatistics: false,
+  numberOfStations: 1,
   ocppStrictCompliance: true,
-  enableStatistics: false
+  persistentConfiguration: true,
+  renderTemplates: randomUUID(),
+  supervisionUrl: '',
+  template: '',
 })
 
-watch(getCurrentInstance()!.appContext.config.globalProperties.$templates, () => {
+watch(getCurrentInstance()!.appContext.config.globalProperties!.$templates, () => {
   state.value.renderTemplates = randomUUID()
 })
 </script>
index edefc8a544c8d4eafd86404a07f7b2fc7a2c98ff..ae62b05f2d53441e28ba81163a8b459b2f4e8de5 100644 (file)
@@ -1,21 +1,23 @@
 <template>
-  <h1 id="action">Set Supervision Url</h1>
+  <h1 id="action">
+    Set Supervision Url
+  </h1>
   <h2>{{ chargingStationId }}</h2>
   <p>Supervision Url:</p>
   <input
     id="supervision-url"
     v-model.trim="state.supervisionUrl"
-    type="url"
     name="supervision-url"
     placeholder="wss://"
-  />
-  <br />
+    type="url"
+  >
+  <br>
   <Button
     id="action-button"
     @click="
       () => {
         $uiClient
-          .setSupervisionUrl(hashId, state.supervisionUrl)
+          ?.setSupervisionUrl(hashId, state.supervisionUrl)
           .then(() => {
             $toast.success('Supervision url successfully set')
           })
@@ -39,12 +41,12 @@ import { ref } from 'vue'
 import Button from '@/components/buttons/Button.vue'
 
 defineProps<{
-  hashId: string
   chargingStationId: string
+  hashId: string
 }>()
 
 const state = ref<{ supervisionUrl: string }>({
-  supervisionUrl: ''
+  supervisionUrl: '',
 })
 </script>
 
index a0ab5ceceff52094a03e0b3ca34f1eb1fe46ab5b..d6829d46a6c88fd8725d40c44168a73b58ba2106 100644 (file)
@@ -1,16 +1,24 @@
 <template>
-  <h1 id="action">Start Transaction</h1>
+  <h1 id="action">
+    Start Transaction
+  </h1>
   <h2>{{ chargingStationId }}</h2>
   <h3>Connector {{ connectorId }}</h3>
   <p>Scan RFID tag:</p>
-  <input id="idtag" v-model.trim="state.idTag" type="text" name="idtag" placeholder="RFID tag" />
-  <br />
+  <input
+    id="idtag"
+    v-model.trim="state.idTag"
+    name="idtag"
+    placeholder="RFID tag"
+    type="text"
+  >
+  <br>
   <Button
     id="action-button"
     @click="
       () => {
         $uiClient
-          .startTransaction(hashId, convertToInt(connectorId), state.idTag)
+          ?.startTransaction(hashId, convertToInt(connectorId), state.idTag)
           .then(() => {
             $toast.success('Transaction successfully started')
           })
@@ -35,13 +43,13 @@ import Button from '@/components/buttons/Button.vue'
 import { convertToInt } from '@/composables'
 
 defineProps<{
-  hashId: string
   chargingStationId: string
   connectorId: string
+  hashId: string
 }>()
 
 const state = ref<{ idTag: string }>({
-  idTag: ''
+  idTag: '',
 })
 </script>
 
index 9d99cdb4e8cf424bbf1a87e9763d448b7cf29319..708d6c448b2dec262c23d8ba11772069a49a3ccd 100644 (file)
@@ -1,6 +1,9 @@
 <template>
-  <button type="button" class="button">
-    <slot></slot>
+  <button
+    class="button"
+    type="button"
+  >
+    <slot />
   </button>
 </template>
 
index 537a198b26ef6f1b9763b8f4a37583f92fe2c8bc..de1d269255a2f415981ddf483aa6b0f7f9e908a6 100644 (file)
@@ -1,6 +1,9 @@
 <template>
-  <Button :class="{ on: state.status }" @click="click()">
-    <slot></slot>
+  <Button
+    :class="{ on: state.status }"
+    @click="click()"
+  >
+    <slot />
   </Button>
 </template>
 
@@ -12,10 +15,10 @@ import { getFromLocalStorage, setToLocalStorage } from '@/composables'
 
 const props = defineProps<{
   id: string
-  status?: boolean
-  shared?: boolean
-  on?: () => void
   off?: () => void
+  on?: () => void
+  shared?: boolean
+  status?: boolean
 }>()
 
 const $emit = defineEmits(['clicked'])
@@ -23,7 +26,7 @@ const $emit = defineEmits(['clicked'])
 const id = props.shared === true ? `shared-toggle-button-${props.id}` : `toggle-button-${props.id}`
 
 const state = ref<{ status: boolean }>({
-  status: getFromLocalStorage<boolean>(id, props.status ?? false)
+  status: getFromLocalStorage<boolean>(id, props.status ?? false),
 })
 
 const click = (): void => {
index 9bfeae6de04620968bfd037e21ba4b1d5c9b8d9a..a6a5729211ea4dbe4888a32cb0e675b1671553dc 100644 (file)
@@ -1,9 +1,13 @@
 <template>
   <tr class="connectors-table__row">
-    <td class="connectors-table__column">{{ connectorId }}</td>
-    <td class="connectors-table__column">{{ connector.status ?? 'Ø' }}</td>
     <td class="connectors-table__column">
-      {{ connector.transactionStarted === true ? 'Yes' : 'No' }}
+      {{ connectorId }}
+    </td>
+    <td class="connectors-table__column">
+      {{ connector.status ?? 'Ø' }}
+    </td>
+    <td class="connectors-table__column">
+      {{ connector.transactionStarted === true ? `Yes (${connector.transactionId})` : 'No' }}
     </td>
     <td class="connectors-table__column">
       {{ atgStatus?.start === true ? 'Yes' : 'No' }}
     <td class="connectors-table__column">
       <ToggleButton
         :id="`${hashId}-${connectorId}-start-transaction`"
-        :shared="true"
-        :on="
-          () => {
-            $router.push({
-              name: 'start-transaction',
-              params: { hashId, chargingStationId, connectorId }
-            })
-          }
-        "
         :off="
           () => {
             $router.push({ name: 'charging-stations' })
           }
         "
-        @clicked="
+        :on="
           () => {
-            $emit('need-refresh')
+            $router.push({
+              name: 'start-transaction',
+              params: { hashId, chargingStationId, connectorId },
+            })
           }
         "
+        :shared="true"
+        @clicked="$emit('need-refresh')"
       >
         Start Transaction
       </ToggleButton>
-      <Button @click="stopTransaction()">Stop Transaction</Button>
-      <Button @click="startAutomaticTransactionGenerator()">Start ATG</Button>
-      <Button @click="stopAutomaticTransactionGenerator()">Stop ATG</Button>
+      <Button @click="stopTransaction()">
+        Stop Transaction
+      </Button>
+      <Button @click="startAutomaticTransactionGenerator()">
+        Start ATG
+      </Button>
+      <Button @click="stopAutomaticTransactionGenerator()">
+        Stop ATG
+      </Button>
     </td>
   </tr>
 </template>
 <script setup lang="ts">
 import { useToast } from 'vue-toast-notification'
 
+import type { ConnectorStatus, Status } from '@/types'
+
 import Button from '@/components/buttons/Button.vue'
 import ToggleButton from '@/components/buttons/ToggleButton.vue'
 import { useUIClient } from '@/composables'
-import type { ConnectorStatus, Status } from '@/types'
 
 const props = defineProps<{
-  hashId: string
+  atgStatus?: Status
   chargingStationId: string
-  connectorId: number
   connector: ConnectorStatus
-  atgStatus?: Status
+  connectorId: number
+  hashId: string
 }>()
 
 const $emit = defineEmits(['need-refresh'])
@@ -66,7 +73,7 @@ const stopTransaction = (): void => {
   uiClient
     .stopTransaction(props.hashId, props.connector.transactionId)
     .then(() => {
-      $toast.success('Transaction successfully stopped')
+      return $toast.success('Transaction successfully stopped')
     })
     .catch((error: Error) => {
       $toast.error('Error at stopping transaction')
@@ -77,7 +84,7 @@ const startAutomaticTransactionGenerator = (): void => {
   uiClient
     .startAutomaticTransactionGenerator(props.hashId, props.connectorId)
     .then(() => {
-      $toast.success('Automatic transaction generator successfully started')
+      return $toast.success('Automatic transaction generator successfully started')
     })
     .catch((error: Error) => {
       $toast.error('Error at starting automatic transaction generator')
@@ -88,7 +95,7 @@ const stopAutomaticTransactionGenerator = (): void => {
   uiClient
     .stopAutomaticTransactionGenerator(props.hashId, props.connectorId)
     .then(() => {
-      $toast.success('Automatic transaction generator successfully stopped')
+      return $toast.success('Automatic transaction generator successfully stopped')
     })
     .catch((error: Error) => {
       $toast.error('Error at stopping automatic transaction generator')
index 48edd609b022ae81d6a8d03f6f4b2696baa5fd7a..3310c36a0dd9c6e33d25161bdadc1266a199f133 100644 (file)
     <td class="cs-table__column">
       {{ chargingStation.stationInfo.chargingStationId }}
     </td>
-    <td class="cs-table__column">{{ chargingStation.started === true ? 'Yes' : 'No' }}</td>
+    <td class="cs-table__column">
+      {{ chargingStation.started === true ? 'Yes' : 'No' }}
+    </td>
     <td class="cs-table__column">
       {{ getSupervisionUrl() }}
     </td>
-    <td class="cs-table__column">{{ getWSState() }}</td>
+    <td class="cs-table__column">
+      {{ getWSState() }}
+    </td>
     <td class="cs-table__column">
       {{ chargingStation.bootNotificationResponse?.status ?? 'Ø' }}
     </td>
     <td class="cs-table__column">
       {{ chargingStation.stationInfo.templateName }}
     </td>
-    <td class="cs-table__column">{{ chargingStation.stationInfo.chargePointVendor }}</td>
-    <td class="cs-table__column">{{ chargingStation.stationInfo.chargePointModel }}</td>
+    <td class="cs-table__column">
+      {{ chargingStation.stationInfo.chargePointVendor }}
+    </td>
+    <td class="cs-table__column">
+      {{ chargingStation.stationInfo.chargePointModel }}
+    </td>
     <td class="cs-table__column">
       {{ chargingStation.stationInfo.firmwareVersion ?? 'Ø' }}
     </td>
     <td class="cs-table__column">
-      <Button @click="startChargingStation()">Start Charging Station</Button>
-      <Button @click="stopChargingStation()">Stop Charging Station</Button>
+      <Button @click="startChargingStation()">
+        Start Charging Station
+      </Button>
+      <Button @click="stopChargingStation()">
+        Stop Charging Station
+      </Button>
       <ToggleButton
         :id="`${chargingStation.stationInfo.hashId}-set-supervision-url`"
-        :shared="true"
+        :off="
+          () => {
+            $router.push({ name: 'charging-stations' })
+          }
+        "
         :on="
           () => {
             $router.push({
               name: 'set-supervision-url',
               params: {
                 hashId: chargingStation.stationInfo.hashId,
-                chargingStationId: chargingStation.stationInfo.chargingStationId
-              }
+                chargingStationId: chargingStation.stationInfo.chargingStationId,
+              },
             })
           }
         "
-        :off="
-          () => {
-            $router.push({ name: 'charging-stations' })
-          }
-        "
-        @clicked="
-          () => {
-            $emit('need-refresh')
-          }
-        "
+        :shared="true"
+        @clicked="$emit('need-refresh')"
       >
         Set Supervision Url
       </ToggleButton>
-      <Button @click="openConnection()">Open Connection</Button>
-      <Button @click="closeConnection()">Close Connection</Button>
-      <Button @click="deleteChargingStation()">Delete Charging Station</Button>
+      <Button @click="openConnection()">
+        Open Connection
+      </Button>
+      <Button @click="closeConnection()">
+        Close Connection
+      </Button>
+      <Button @click="deleteChargingStation()">
+        Delete Charging Station
+      </Button>
     </td>
     <td class="cs-table__connectors-column">
       <table id="connectors-table">
-        <caption></caption>
+        <caption />
         <thead id="connectors-table__head">
           <tr class="connectors-table__row">
-            <th scope="col" class="connectors-table__column">Identifier</th>
-            <th scope="col" class="connectors-table__column">Status</th>
-            <th scope="col" class="connectors-table__column">Transaction</th>
-            <th scope="col" class="connectors-table__column">ATG Started</th>
-            <th scope="col" class="connectors-table__column">Actions</th>
+            <th
+              class="connectors-table__column"
+              scope="col"
+            >
+              Identifier
+            </th>
+            <th
+              class="connectors-table__column"
+              scope="col"
+            >
+              Status
+            </th>
+            <th
+              class="connectors-table__column"
+              scope="col"
+            >
+              Transaction
+            </th>
+            <th
+              class="connectors-table__column"
+              scope="col"
+            >
+              ATG Started
+            </th>
+            <th
+              class="connectors-table__column"
+              scope="col"
+            >
+              Actions
+            </th>
           </tr>
         </thead>
         <tbody id="connectors-table__body">
           <CSConnector
             v-for="(connector, index) in getConnectorStatuses()"
             :key="index + 1"
-            :hash-id="chargingStation.stationInfo.hashId"
+            :atg-status="getATGStatus(index + 1)"
             :charging-station-id="chargingStation.stationInfo.chargingStationId"
-            :connector-id="index + 1"
             :connector="connector"
-            :atg-status="getATGStatus(index + 1)"
+            :connector-id="index + 1"
+            :hash-id="chargingStation.stationInfo.hashId"
             @need-refresh="$emit('need-refresh')"
           />
         </tbody>
 <script setup lang="ts">
 import { useToast } from 'vue-toast-notification'
 
+import type { ChargingStationData, ConnectorStatus, Status } from '@/types'
+
 import Button from '@/components/buttons/Button.vue'
 import ToggleButton from '@/components/buttons/ToggleButton.vue'
 import CSConnector from '@/components/charging-stations/CSConnector.vue'
-import { useUIClient } from '@/composables'
-import type { ChargingStationData, ConnectorStatus, Status } from '@/types'
+import { deleteFromLocalStorage, getLocalStorage, useUIClient } from '@/composables'
 
 const props = defineProps<{
   chargingStation: ChargingStationData
@@ -121,14 +161,14 @@ const getSupervisionUrl = (): string => {
 }
 const getWSState = (): string => {
   switch (props.chargingStation?.wsState) {
+    case WebSocket.CLOSED:
+      return 'Closed'
+    case WebSocket.CLOSING:
+      return 'Closing'
     case WebSocket.CONNECTING:
       return 'Connecting'
     case WebSocket.OPEN:
       return 'Open'
-    case WebSocket.CLOSING:
-      return 'Closing'
-    case WebSocket.CLOSED:
-      return 'Closed'
     default:
       return 'Ø'
   }
@@ -142,7 +182,7 @@ const startChargingStation = (): void => {
   uiClient
     .startChargingStation(props.chargingStation.stationInfo.hashId)
     .then(() => {
-      $toast.success('Charging station successfully started')
+      return $toast.success('Charging station successfully started')
     })
     .catch((error: Error) => {
       $toast.error('Error at starting charging station')
@@ -153,7 +193,7 @@ const stopChargingStation = (): void => {
   uiClient
     .stopChargingStation(props.chargingStation.stationInfo.hashId)
     .then(() => {
-      $toast.success('Charging station successfully stopped')
+      return $toast.success('Charging station successfully stopped')
     })
     .catch((error: Error) => {
       $toast.error('Error at stopping charging station')
@@ -164,7 +204,7 @@ const openConnection = (): void => {
   uiClient
     .openConnection(props.chargingStation.stationInfo.hashId)
     .then(() => {
-      $toast.success('Connection successfully opened')
+      return $toast.success('Connection successfully opened')
     })
     .catch((error: Error) => {
       $toast.error('Error at opening connection')
@@ -175,7 +215,7 @@ const closeConnection = (): void => {
   uiClient
     .closeConnection(props.chargingStation.stationInfo.hashId)
     .then(() => {
-      $toast.success('Connection successfully closed')
+      return $toast.success('Connection successfully closed')
     })
     .catch((error: Error) => {
       $toast.error('Error at closing connection')
@@ -186,7 +226,12 @@ const deleteChargingStation = (): void => {
   uiClient
     .deleteChargingStation(props.chargingStation.stationInfo.hashId)
     .then(() => {
-      $toast.success('Charging station successfully deleted')
+      for (const key in getLocalStorage()) {
+        if (key.includes(props.chargingStation.stationInfo.hashId)) {
+          deleteFromLocalStorage(key)
+        }
+      }
+      return $toast.success('Charging station successfully deleted')
     })
     .catch((error: Error) => {
       $toast.error('Error at deleting charging station')
index 3175d6501974b2b3c7afa5251a957e452a5a7c59..0902cd7d3b23d30071013fbb949460cb2dfbb3cd 100644 (file)
@@ -5,17 +5,72 @@
     </caption>
     <thead id="cs-table__head">
       <tr class="cs-table__row">
-        <th scope="col" class="cs-table__column">Name</th>
-        <th scope="col" class="cs-table__column">Started</th>
-        <th scope="col" class="cs-table__column">Supervision Url</th>
-        <th scope="col" class="cs-table__column">WebSocket State</th>
-        <th scope="col" class="cs-table__column">Registration Status</th>
-        <th scope="col" class="cs-table__column">Template</th>
-        <th scope="col" class="cs-table__column">Vendor</th>
-        <th scope="col" class="cs-table__column">Model</th>
-        <th scope="col" class="cs-table__column">Firmware</th>
-        <th scope="col" class="cs-table__column">Actions</th>
-        <th scope="col" class="cs-table__connectors-column">Connector(s)</th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Name
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Started
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Supervision Url
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          WebSocket State
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Registration Status
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Template
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Vendor
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Model
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Firmware
+        </th>
+        <th
+          class="cs-table__column"
+          scope="col"
+        >
+          Actions
+        </th>
+        <th
+          class="cs-table__connectors-column"
+          scope="col"
+        >
+          Connector(s)
+        </th>
       </tr>
     </thead>
     <tbody id="cs-table__body">
 </template>
 
 <script setup lang="ts">
-import CSData from '@/components/charging-stations/CSData.vue'
 import type { ChargingStationData } from '@/types'
 
+import CSData from '@/components/charging-stations/CSData.vue'
+
 defineProps<{
   chargingStations: ChargingStationData[]
 }>()
index 9522c59213c666a526d48927447024f3e8fc065a..60e6c66564be7740fe521dfaade058e221071a82 100644 (file)
@@ -9,27 +9,27 @@ import {
   type RequestPayload,
   type ResponsePayload,
   ResponseStatus,
-  type UIServerConfigurationSection
+  type UIServerConfigurationSection,
 } from '@/types'
 
 import { randomUUID, validateUUID } from './Utils'
 
-type ResponseHandler = {
+interface ResponseHandler {
   procedureName: ProcedureName
-  resolve: (value: ResponsePayload | PromiseLike<ResponsePayload>) => void
   reject: (reason?: unknown) => void
+  resolve: (value: PromiseLike<ResponsePayload> | ResponsePayload) => void
 }
 
 export class UIClient {
-  private static instance: UIClient | null = null
-
-  private ws?: WebSocket
+  private static instance: null | UIClient = null
   private responseHandlers: Map<
     `${string}-${string}-${string}-${string}-${string}`,
     ResponseHandler
   >
 
-  private constructor(private uiServerConfiguration: UIServerConfigurationSection) {
+  private ws?: WebSocket
+
+  private constructor (private uiServerConfiguration: UIServerConfigurationSection) {
     this.openWS()
     this.responseHandlers = new Map<
       `${string}-${string}-${string}-${string}-${string}`,
@@ -37,7 +37,7 @@ export class UIClient {
     >()
   }
 
-  public static getInstance(uiServerConfiguration?: UIServerConfigurationSection): UIClient {
+  public static getInstance (uiServerConfiguration?: UIServerConfigurationSection): UIClient {
     if (UIClient.instance === null) {
       if (uiServerConfiguration == null) {
         throw new Error('Cannot initialize UIClient if no configuration is provided')
@@ -47,198 +47,187 @@ export class UIClient {
     return UIClient.instance
   }
 
-  public setConfiguration(uiServerConfiguration: UIServerConfigurationSection): void {
-    if (this.ws?.readyState === WebSocket.OPEN) {
-      this.ws.close()
-      delete this.ws
-    }
-    this.uiServerConfiguration = uiServerConfiguration
-    this.openWS()
-  }
-
-  public registerWSEventListener<K extends keyof WebSocketEventMap>(
-    event: K,
-    listener: (event: WebSocketEventMap[K]) => void,
-    options?: boolean | AddEventListenerOptions
-  ) {
-    this.ws?.addEventListener(event, listener, options)
-  }
-
-  public unregisterWSEventListener<K extends keyof WebSocketEventMap>(
-    event: K,
-    listener: (event: WebSocketEventMap[K]) => void,
-    options?: boolean | AddEventListenerOptions
-  ) {
-    this.ws?.removeEventListener(event, listener, options)
+  public async addChargingStations (
+    template: string,
+    numberOfStations: number,
+    options?: ChargingStationOptions
+  ): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.ADD_CHARGING_STATIONS, {
+      numberOfStations,
+      options,
+      template,
+    })
   }
 
-  public async simulatorState(): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.SIMULATOR_STATE, {})
+  public async closeConnection (hashId: string): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
+      hashIds: [hashId],
+    })
   }
 
-  public async startSimulator(): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.START_SIMULATOR, {})
+  public async deleteChargingStation (hashId: string): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.DELETE_CHARGING_STATIONS, {
+      hashIds: [hashId],
+    })
   }
 
-  public async stopSimulator(): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.STOP_SIMULATOR, {})
+  public async listChargingStations (): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {})
   }
 
-  public async listTemplates(): Promise<ResponsePayload> {
+  public async listTemplates (): Promise<ResponsePayload> {
     return this.sendRequest(ProcedureName.LIST_TEMPLATES, {})
   }
 
-  public async listChargingStations(): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.LIST_CHARGING_STATIONS, {})
+  public async openConnection (hashId: string): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
+      hashIds: [hashId],
+    })
   }
 
-  public async addChargingStations(
-    template: string,
-    numberOfStations: number,
-    options?: ChargingStationOptions
-  ): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.ADD_CHARGING_STATIONS, {
-      template,
-      numberOfStations,
-      options
-    })
+  public registerWSEventListener<K extends keyof WebSocketEventMap>(
+    event: K,
+    listener: (event: WebSocketEventMap[K]) => void,
+    options?: AddEventListenerOptions | boolean
+  ) {
+    this.ws?.addEventListener(event, listener, options)
   }
 
-  public async deleteChargingStation(hashId: string): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.DELETE_CHARGING_STATIONS, { hashIds: [hashId] })
+  public setConfiguration (uiServerConfiguration: UIServerConfigurationSection): void {
+    if (this.ws?.readyState === WebSocket.OPEN) {
+      this.ws.close()
+      delete this.ws
+    }
+    this.uiServerConfiguration = uiServerConfiguration
+    this.openWS()
   }
 
-  public async setSupervisionUrl(hashId: string, supervisionUrl: string): Promise<ResponsePayload> {
+  public async setSupervisionUrl (hashId: string, supervisionUrl: string): Promise<ResponsePayload> {
     return this.sendRequest(ProcedureName.SET_SUPERVISION_URL, {
       hashIds: [hashId],
-      url: supervisionUrl
+      url: supervisionUrl,
     })
   }
 
-  public async startChargingStation(hashId: string): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.START_CHARGING_STATION, { hashIds: [hashId] })
+  public async simulatorState (): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.SIMULATOR_STATE, {})
   }
 
-  public async stopChargingStation(hashId: string): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, { hashIds: [hashId] })
+  public async startAutomaticTransactionGenerator (
+    hashId: string,
+    connectorId: number
+  ): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, {
+      connectorIds: [connectorId],
+      hashIds: [hashId],
+    })
   }
 
-  public async openConnection(hashId: string): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.OPEN_CONNECTION, {
-      hashIds: [hashId]
+  public async startChargingStation (hashId: string): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.START_CHARGING_STATION, {
+      hashIds: [hashId],
     })
   }
 
-  public async closeConnection(hashId: string): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.CLOSE_CONNECTION, {
-      hashIds: [hashId]
-    })
+  public async startSimulator (): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.START_SIMULATOR, {})
   }
 
-  public async startTransaction(
+  public async startTransaction (
     hashId: string,
     connectorId: number,
     idTag: string | undefined
   ): Promise<ResponsePayload> {
     return this.sendRequest(ProcedureName.START_TRANSACTION, {
-      hashIds: [hashId],
       connectorId,
-      idTag
+      hashIds: [hashId],
+      idTag,
     })
   }
 
-  public async stopTransaction(
+  public async stopAutomaticTransactionGenerator (
     hashId: string,
-    transactionId: number | undefined
+    connectorId: number
   ): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
+    return this.sendRequest(ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, {
+      connectorIds: [connectorId],
       hashIds: [hashId],
-      transactionId
     })
   }
 
-  public async startAutomaticTransactionGenerator(
-    hashId: string,
-    connectorId: number
-  ): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR, {
+  public async stopChargingStation (hashId: string): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.STOP_CHARGING_STATION, {
       hashIds: [hashId],
-      connectorIds: [connectorId]
     })
   }
 
-  public async stopAutomaticTransactionGenerator(
+  public async stopSimulator (): Promise<ResponsePayload> {
+    return this.sendRequest(ProcedureName.STOP_SIMULATOR, {})
+  }
+
+  public async stopTransaction (
     hashId: string,
-    connectorId: number
+    transactionId: number | undefined
   ): Promise<ResponsePayload> {
-    return this.sendRequest(ProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR, {
+    return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
       hashIds: [hashId],
-      connectorIds: [connectorId]
+      transactionId,
     })
   }
 
-  private openWS(): void {
+  public unregisterWSEventListener<K extends keyof WebSocketEventMap>(
+    event: K,
+    listener: (event: WebSocketEventMap[K]) => void,
+    options?: AddEventListenerOptions | boolean
+  ) {
+    this.ws?.removeEventListener(event, listener, options)
+  }
+
+  private openWS (): void {
     const protocols =
       this.uiServerConfiguration.authentication?.enabled === true &&
-      this.uiServerConfiguration.authentication?.type === AuthenticationType.PROTOCOL_BASIC_AUTH
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      this.uiServerConfiguration.authentication.type === AuthenticationType.PROTOCOL_BASIC_AUTH
         ? [
             `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`,
-            `authorization.basic.${btoa(`${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`).replace(/={1,2}$/, '')}`
+            `authorization.basic.${btoa(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `${this.uiServerConfiguration.authentication.username}:${this.uiServerConfiguration.authentication.password}`
+            ).replace(/={1,2}$/, '')}`,
           ]
         : `${this.uiServerConfiguration.protocol}${this.uiServerConfiguration.version}`
     this.ws = new WebSocket(
-      `${this.uiServerConfiguration.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port}`,
+      `${
+        this.uiServerConfiguration.secure === true
+          ? ApplicationProtocol.WSS
+          : ApplicationProtocol.WS
+      }://${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}`,
       protocols
     )
     this.ws.onopen = () => {
       useToast().success(
-        `WebSocket to UI server '${this.uiServerConfiguration.host}' successfully opened`
+        `WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}' successfully opened`
       )
     }
     this.ws.onmessage = this.responseHandler.bind(this)
     this.ws.onerror = errorEvent => {
-      useToast().error(`Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`)
+      useToast().error(
+        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`
+      )
       console.error(
-        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}'`,
+        `Error in WebSocket to UI server '${this.uiServerConfiguration.host}:${this.uiServerConfiguration.port.toString()}'`,
         errorEvent
       )
     }
     this.ws.onclose = () => {
-      useToast().info(`WebSocket to UI server closed`)
+      useToast().info('WebSocket to UI server closed')
     }
   }
 
-  private async sendRequest(
-    procedureName: ProcedureName,
-    payload: RequestPayload
-  ): Promise<ResponsePayload> {
-    return new Promise<ResponsePayload>((resolve, reject) => {
-      if (this.ws?.readyState === WebSocket.OPEN) {
-        const uuid = randomUUID()
-        const msg = JSON.stringify([uuid, procedureName, payload])
-        const sendTimeout = setTimeout(() => {
-          this.responseHandlers.delete(uuid)
-          return reject(new Error(`Send request '${procedureName}' message: connection timeout`))
-        }, 60000)
-        try {
-          this.ws.send(msg)
-          this.responseHandlers.set(uuid, { procedureName, resolve, reject })
-        } catch (error) {
-          this.responseHandlers.delete(uuid)
-          reject(error)
-        } finally {
-          clearTimeout(sendTimeout)
-        }
-      } else {
-        reject(new Error(`Send request '${procedureName}' message: connection closed`))
-      }
-    })
-  }
-
-  private responseHandler(messageEvent: MessageEvent<string>): void {
+  private responseHandler (messageEvent: MessageEvent<string>): void {
     let response: ProtocolResponse
     try {
-      response = JSON.parse(messageEvent.data)
+      response = JSON.parse(messageEvent.data) as ProtocolResponse
     } catch (error) {
       useToast().error('Invalid response JSON format')
       console.error('Invalid response JSON format', error)
@@ -260,17 +249,19 @@ export class UIClient {
     }
 
     if (this.responseHandlers.has(uuid)) {
-      const { procedureName, resolve, reject } = this.responseHandlers.get(uuid)!
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const { procedureName, reject, resolve } = this.responseHandlers.get(uuid)!
       switch (responsePayload.status) {
-        case ResponseStatus.SUCCESS:
-          resolve(responsePayload)
-          break
         case ResponseStatus.FAILURE:
           reject(responsePayload)
           break
+        case ResponseStatus.SUCCESS:
+          resolve(responsePayload)
+          break
         default:
           reject(
             new Error(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
               `Response status for procedure '${procedureName}' not supported: '${responsePayload.status}'`
             )
           )
@@ -280,4 +271,35 @@ export class UIClient {
       throw new Error(`Not a response to a request: ${JSON.stringify(response, undefined, 2)}`)
     }
   }
+
+  private async sendRequest (
+    procedureName: ProcedureName,
+    payload: RequestPayload
+  ): Promise<ResponsePayload> {
+    return new Promise<ResponsePayload>((resolve, reject) => {
+      if (this.ws?.readyState === WebSocket.OPEN) {
+        const uuid = randomUUID()
+        const msg = JSON.stringify([uuid, procedureName, payload])
+        const sendTimeout = setTimeout(() => {
+          this.responseHandlers.delete(uuid)
+          reject(new Error(`Send request '${procedureName}' message: connection timeout`))
+        }, 60000)
+        try {
+          this.ws.send(msg)
+          this.responseHandlers.set(uuid, { procedureName, reject, resolve })
+        } catch (error) {
+          this.responseHandlers.delete(uuid)
+          reject(
+            new Error(
+              `Send request '${procedureName}' message: error ${(error as Error).toString()}`
+            )
+          )
+        } finally {
+          clearTimeout(sendTimeout)
+        }
+      } else {
+        reject(new Error(`Send request '${procedureName}' message: connection closed`))
+      }
+    })
+  }
 }
index 0c8a83996e905c7e046d624ad9a8bd64124d1df1..d34749e4b2f4857042940911b14b55c76c56a52e 100644 (file)
@@ -27,10 +27,11 @@ export const convertToInt = (value: unknown): number => {
   }
   let changedValue: number = value as number
   if (typeof value === 'string') {
-    changedValue = parseInt(value)
+    changedValue = Number.parseInt(value)
   }
-  if (isNaN(changedValue)) {
-    throw new Error(`Cannot convert to integer: '${String(value)}'`)
+  if (Number.isNaN(changedValue)) {
+    // eslint-disable-next-line @typescript-eslint/no-base-to-string
+    throw new Error(`Cannot convert to integer: '${value.toString()}'`)
   }
   return changedValue
 }
@@ -40,6 +41,7 @@ export const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
   return item != null ? (JSON.parse(item) as T) : defaultValue
 }
 
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
 export const setToLocalStorage = <T>(key: string, value: T): void => {
   localStorage.setItem(key, JSON.stringify(value))
 }
index 6810a971b0689d9d21089aa51770860e398ea1ce..f4a165a80a64277ac888a828600c2b5e8586965e 100644 (file)
@@ -7,5 +7,5 @@ export {
   getLocalStorage,
   randomUUID,
   setToLocalStorage,
-  useUIClient
+  useUIClient,
 } from './Utils'
index a39c049ba99052c4e48a2898fe3039508f1aab15..77eb8d1d0b56c8c50638ca1fc335c4e02f4554ed 100644 (file)
@@ -1,14 +1,15 @@
-import 'vue-toast-notification/dist/theme-bootstrap.css'
-
-import { type App as AppType, createApp, ref } from 'vue'
+import { type App as AppType, type Component, createApp, ref } from 'vue'
 import ToastPlugin from 'vue-toast-notification'
 
+import type { ChargingStationData, ConfigurationData, UIServerConfigurationSection } from '@/types'
+
 import App from '@/App.vue'
 import { getFromLocalStorage, setToLocalStorage, UIClient } from '@/composables'
 import { router } from '@/router'
-import type { ChargingStationData, ConfigurationData, UIServerConfigurationSection } from '@/types'
 
-const app = createApp(App)
+import 'vue-toast-notification/dist/theme-bootstrap.css'
+
+const app = createApp(App as Component)
 
 const initializeApp = (app: AppType, config: ConfigurationData) => {
   app.config.errorHandler = (error, instance, info) => {
@@ -20,9 +21,7 @@ const initializeApp = (app: AppType, config: ConfigurationData) => {
   if (!Array.isArray(config.uiServer)) {
     config.uiServer = [config.uiServer]
   }
-  if (app.config.globalProperties.$configuration == null) {
-    app.config.globalProperties.$configuration = ref<ConfigurationData>(config)
-  }
+  app.config.globalProperties.$configuration ??= ref<ConfigurationData>(config)
   if (!Array.isArray(app.config.globalProperties.$templates?.value)) {
     app.config.globalProperties.$templates = ref<string[]>([])
   }
@@ -38,13 +37,11 @@ const initializeApp = (app: AppType, config: ConfigurationData) => {
   ) {
     setToLocalStorage<number>('uiServerConfigurationIndex', 0)
   }
-  if (app.config.globalProperties.$uiClient == null) {
-    app.config.globalProperties.$uiClient = UIClient.getInstance(
-      (app.config.globalProperties.$configuration.value.uiServer as UIServerConfigurationSection[])[
-        getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
-      ]
-    )
-  }
+  app.config.globalProperties.$uiClient ??= UIClient.getInstance(
+    (app.config.globalProperties.$configuration.value.uiServer as UIServerConfigurationSection[])[
+      getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
+    ]
+  )
   app.use(router).use(ToastPlugin).mount('#app')
 }
 
@@ -53,19 +50,23 @@ fetch('/config.json')
     if (!response.ok) {
       // TODO: add code for UI notifications or other error handling logic
       console.error('Failed to fetch app configuration')
-      return
+      return undefined
     }
     response
       .json()
+      // eslint-disable-next-line promise/no-nesting
       .then(config => {
-        initializeApp(app, config)
+        initializeApp(app, config as ConfigurationData)
+        return undefined
       })
-      .catch(error => {
+      // eslint-disable-next-line promise/no-nesting
+      .catch((error: unknown) => {
         // TODO: add code for UI notifications or other error handling logic
         console.error('Error at deserializing JSON app configuration:', error)
       })
+    return undefined
   })
-  .catch(error => {
+  .catch((error: unknown) => {
     // TODO: add code for UI notifications or other error handling logic
     console.error('Error at fetching app configuration:', error)
   })
index cee4f416eb51cf9c5659bcd150bed263a7bfef7f..a3eb54d177c6bc56f9ee43688b60bf74015b783b 100644 (file)
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unsafe-assignment */
 import { createRouter, createWebHistory } from 'vue-router'
 
 import AddChargingStations from '@/components/actions/AddChargingStations.vue'
@@ -10,44 +11,44 @@ export const router = createRouter({
   history: createWebHistory(),
   routes: [
     {
-      path: '/',
-      name: 'charging-stations',
       components: {
-        default: ChargingStationsView
-      }
+        default: ChargingStationsView,
+      },
+      name: 'charging-stations',
+      path: '/',
     },
     {
-      path: '/add-charging-stations',
-      name: 'add-charging-stations',
       components: {
+        action: AddChargingStations,
         default: ChargingStationsView,
-        action: AddChargingStations
-      }
+      },
+      name: 'add-charging-stations',
+      path: '/add-charging-stations',
     },
     {
-      path: '/set-supervision-url/:hashId/:chargingStationId',
-      name: 'set-supervision-url',
       components: {
+        action: SetSupervisionUrl,
         default: ChargingStationsView,
-        action: SetSupervisionUrl
       },
-      props: { default: false, action: true }
+      name: 'set-supervision-url',
+      path: '/set-supervision-url/:hashId/:chargingStationId',
+      props: { action: true, default: false },
     },
     {
-      path: '/start-transaction/:hashId/:chargingStationId/:connectorId',
-      name: 'start-transaction',
       components: {
+        action: StartTransaction,
         default: ChargingStationsView,
-        action: StartTransaction
       },
-      props: { default: false, action: true }
+      name: 'start-transaction',
+      path: '/start-transaction/:hashId/:chargingStationId/:connectorId',
+      props: { action: true, default: false },
     },
     {
+      components: {
+        default: NotFoundView,
+      },
       name: 'not-found',
       path: '/:pathMatch(.*)*',
-      components: {
-        default: NotFoundView
-      }
-    }
-  ]
+    },
+  ],
 })
index 6f0d76e38bac14a980a967753b9db65609cc0a46..13685ebb1076c6fcdd0f9e996214265d624b3b60 100644 (file)
@@ -1,4 +1,4 @@
-export {}
+export type {}
 
 declare module 'vue' {
   export interface GlobalComponents {
@@ -6,9 +6,9 @@ declare module 'vue' {
     RouterView: (typeof import('vue-router'))['RouterView']
   }
   interface ComponentCustomProperties {
-    $configuration: import('vue').Ref<import('@/types').ConfigurationData>
-    $templates: import('vue').Ref<string[]>
-    $chargingStations: import('vue').Ref<import('@/types').ChargingStationData[]>
-    $uiClient: import('@/composables').UIClient
+    $chargingStations: import('vue').Ref<import('@/types').ChargingStationData[]> | undefined
+    $configuration: import('vue').Ref<import('@/types').ConfigurationData> | undefined
+    $templates: import('vue').Ref<string[]> | undefined
+    $uiClient: import('@/composables').UIClient | undefined
   }
 }
index 167e3a1a8498b79c272bd3792be6ced370e32199..357d177d56f78745f798cc3de62a438a942f47b4 100644 (file)
 import type { JsonObject } from './JsonType'
 
+export enum AmpereUnits {
+  AMPERE = 'A',
+  CENTI_AMPERE = 'cA',
+  DECI_AMPERE = 'dA',
+  MILLI_AMPERE = 'mA',
+}
+
+export enum CurrentType {
+  AC = 'AC',
+  DC = 'DC',
+}
+
 export enum IdTagDistribution {
+  CONNECTOR_AFFINITY = 'connector-affinity',
   RANDOM = 'random',
   ROUND_ROBIN = 'round-robin',
-  CONNECTOR_AFFINITY = 'connector-affinity'
+}
+
+export enum OCPP16AvailabilityType {
+  INOPERATIVE = 'Inoperative',
+  OPERATIVE = 'Operative',
+}
+
+export enum OCPP16ChargePointStatus {
+  AVAILABLE = 'Available',
+  CHARGING = 'Charging',
+  FAULTED = 'Faulted',
+  FINISHING = 'Finishing',
+  OCCUPIED = 'Occupied',
+  PREPARING = 'Preparing',
+  RESERVED = 'Reserved',
+  SUSPENDED_EV = 'SuspendedEV',
+  SUSPENDED_EVSE = 'SuspendedEVSE',
+  UNAVAILABLE = 'Unavailable',
+}
+
+export enum OCPP16FirmwareStatus {
+  Downloaded = 'Downloaded',
+  DownloadFailed = 'DownloadFailed',
+  Downloading = 'Downloading',
+  Idle = 'Idle',
+  InstallationFailed = 'InstallationFailed',
+  Installed = 'Installed',
+  Installing = 'Installing',
+}
+
+export enum OCPP16IncomingRequestCommand {
+  CHANGE_AVAILABILITY = 'ChangeAvailability',
+  CHANGE_CONFIGURATION = 'ChangeConfiguration',
+  CLEAR_CACHE = 'ClearCache',
+  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+  GET_CONFIGURATION = 'GetConfiguration',
+  GET_DIAGNOSTICS = 'GetDiagnostics',
+  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
+  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
+  RESET = 'Reset',
+  SET_CHARGING_PROFILE = 'SetChargingProfile',
+  TRIGGER_MESSAGE = 'TriggerMessage',
+  UNLOCK_CONNECTOR = 'UnlockConnector',
+}
+
+export enum OCPP16MessageTrigger {
+  BootNotification = 'BootNotification',
+  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
+  FirmwareStatusNotification = 'FirmwareStatusNotification',
+  Heartbeat = 'Heartbeat',
+  MeterValues = 'MeterValues',
+  StatusNotification = 'StatusNotification',
+}
+
+export enum OCPP16RegistrationStatus {
+  ACCEPTED = 'Accepted',
+  PENDING = 'Pending',
+  REJECTED = 'Rejected',
+}
+
+export enum OCPP16RequestCommand {
+  AUTHORIZE = 'Authorize',
+  BOOT_NOTIFICATION = 'BootNotification',
+  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
+  HEARTBEAT = 'Heartbeat',
+  METER_VALUES = 'MeterValues',
+  START_TRANSACTION = 'StartTransaction',
+  STATUS_NOTIFICATION = 'StatusNotification',
+  STOP_TRANSACTION = 'StopTransaction',
+}
+
+export enum OCPPProtocol {
+  JSON = 'json',
+}
+
+export enum OCPPVersion {
+  VERSION_16 = '1.6',
+  VERSION_20 = '2.0',
+  VERSION_201 = '2.0.1',
+}
+
+export enum Voltage {
+  VOLTAGE_110 = 110,
+  VOLTAGE_230 = 230,
+  VOLTAGE_400 = 400,
+  VOLTAGE_800 = 800,
 }
 
 export interface AutomaticTransactionGeneratorConfiguration extends JsonObject {
   enable: boolean
-  minDuration: number
+  idTagDistribution?: IdTagDistribution
+  maxDelayBetweenTwoTransactions: number
   maxDuration: number
   minDelayBetweenTwoTransactions: number
-  maxDelayBetweenTwoTransactions: number
+  minDuration: number
   probabilityOfStart: number
-  stopAfterHours: number
-  stopAbsoluteDuration: boolean
   requireAuthorize?: boolean
-  idTagDistribution?: IdTagDistribution
+  stopAbsoluteDuration: boolean
+  stopAfterHours: number
 }
 
+export type AvailabilityType = OCPP16AvailabilityType
+
+export type BootNotificationResponse = OCPP16BootNotificationResponse
+
+export type ChargePointStatus = OCPP16ChargePointStatus
+
 export interface ChargingStationAutomaticTransactionGeneratorConfiguration extends JsonObject {
   automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
   automaticTransactionGeneratorStatuses?: Status[]
 }
 
 export interface ChargingStationData extends JsonObject {
-  started: boolean
-  stationInfo: ChargingStationInfo
+  automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
+  bootNotificationResponse?: BootNotificationResponse
   connectors: ConnectorStatus[]
   evses: EvseStatus[]
   ocppConfiguration: ChargingStationOcppConfiguration
+  started: boolean
+  stationInfo: ChargingStationInfo
   supervisionUrl: string
   wsState?:
+    | typeof WebSocket.CLOSED
+    | typeof WebSocket.CLOSING
     | typeof WebSocket.CONNECTING
     | typeof WebSocket.OPEN
-    | typeof WebSocket.CLOSING
-    | typeof WebSocket.CLOSED
-  bootNotificationResponse?: BootNotificationResponse
-  automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
-}
-
-export enum OCPP16FirmwareStatus {
-  Downloaded = 'Downloaded',
-  DownloadFailed = 'DownloadFailed',
-  Downloading = 'Downloading',
-  Idle = 'Idle',
-  InstallationFailed = 'InstallationFailed',
-  Installing = 'Installing',
-  Installed = 'Installed'
-}
-
-export interface FirmwareUpgrade extends JsonObject {
-  versionUpgrade?: {
-    patternGroup?: number
-    step?: number
-  }
-  reset?: boolean
-  failureStatus?: FirmwareStatus
-}
-
-export const FirmwareStatus = {
-  ...OCPP16FirmwareStatus
-} as const
-export type FirmwareStatus = OCPP16FirmwareStatus
-
-export interface ChargingStationOptions extends JsonObject {
-  supervisionUrls?: string | string[]
-  persistentConfiguration?: boolean
-  autoStart?: boolean
-  autoRegister?: boolean
-  enableStatistics?: boolean
-  ocppStrictCompliance?: boolean
-  stopTransactionsOnStopped?: boolean
 }
 
 export interface ChargingStationInfo extends JsonObject {
-  hashId: string
-  templateIndex: number
-  templateName: string
-  chargingStationId: string
-  chargeBoxSerialNumber?: string
-  chargePointSerialNumber?: string
-  meterSerialNumber?: string
-  maximumPower?: number // Always in Watt
-  maximumAmperage?: number // Always in Ampere
-  firmwareStatus?: FirmwareStatus
-  templateHash?: string
-  supervisionUrls?: string | string[]
-  supervisionUrlOcppConfiguration?: boolean
-  supervisionUrlOcppKey?: string
-  supervisionUser?: string
-  supervisionPassword?: string
-  autoStart?: boolean
-  ocppVersion?: OCPPVersion
-  ocppProtocol?: OCPPProtocol
-  ocppStrictCompliance?: boolean
-  ocppPersistentConfiguration?: boolean
-  stationInfoPersistentConfiguration?: boolean
+  amperageLimitationOcppKey?: string
+  amperageLimitationUnit?: AmpereUnits
   automaticTransactionGeneratorPersistentConfiguration?: boolean
-  idTagsFile?: string
+  autoReconnectMaxRetries?: number
+  autoRegister?: boolean
+  autoStart?: boolean
   baseName: string
-  nameSuffix?: string
-  fixedName?: boolean
+  beginEndMeterValues?: boolean
+  chargeBoxSerialNumber?: string
   chargePointModel: string
+  chargePointSerialNumber?: string
   chargePointVendor: string
-  firmwareVersionPattern?: string
-  firmwareVersion?: string
+  chargingStationId: string
+  commandsSupport?: CommandsSupport
+  currentOutType?: CurrentType
+  customValueLimitationMeterValues?: boolean
+  enableStatistics?: boolean
+  firmwareStatus?: FirmwareStatus
   firmwareUpgrade?: FirmwareUpgrade
+  firmwareVersion?: string
+  firmwareVersionPattern?: string
+  fixedName?: boolean
+  hashId: string
   iccid?: string
+  idTagsFile?: string
   imsi?: string
+  mainVoltageMeterValues?: boolean
+  maximumAmperage?: number // Always in Ampere
+  maximumPower?: number // Always in Watt
+  messageTriggerSupport?: Record<MessageTrigger, boolean>
+  meteringPerTransaction?: boolean
+  meterSerialNumber?: string
   meterType?: string
-  powerSharedByConnectors?: boolean
-  currentOutType?: CurrentType
-  voltageOut?: Voltage
+  nameSuffix?: string
   numberOfPhases?: number
-  useConnectorId0?: boolean
+  ocppPersistentConfiguration?: boolean
+  ocppProtocol?: OCPPProtocol
+  ocppStrictCompliance?: boolean
+  ocppVersion?: OCPPVersion
+  outOfOrderEndMeterValues?: boolean
+  phaseLineToLineVoltageMeterValues?: boolean
+  powerSharedByConnectors?: boolean
   randomConnectors?: boolean
-  resetTime?: number
-  autoRegister?: boolean
-  autoReconnectMaxRetries?: number
   reconnectExponentialDelay?: boolean
   registrationMaxRetries?: number
-  enableStatistics?: boolean
   remoteAuthorization?: boolean
-  amperageLimitationOcppKey?: string
-  amperageLimitationUnit?: AmpereUnits
-  beginEndMeterValues?: boolean
-  outOfOrderEndMeterValues?: boolean
-  meteringPerTransaction?: boolean
-  transactionDataMeterValues?: boolean
+  resetTime?: number
+  stationInfoPersistentConfiguration?: boolean
   stopTransactionsOnStopped?: boolean
-  mainVoltageMeterValues?: boolean
-  phaseLineToLineVoltageMeterValues?: boolean
-  customValueLimitationMeterValues?: boolean
-  commandsSupport?: CommandsSupport
-  messageTriggerSupport?: Record<MessageTrigger, boolean>
+  supervisionPassword?: string
+  supervisionUrlOcppConfiguration?: boolean
+  supervisionUrlOcppKey?: string
+  supervisionUrls?: string | string[]
+  supervisionUser?: string
+  templateHash?: string
+  templateIndex: number
+  templateName: string
+  transactionDataMeterValues?: boolean
+  useConnectorId0?: boolean
+  voltageOut?: Voltage
 }
 
 export interface ChargingStationOcppConfiguration extends JsonObject {
   configurationKey?: ConfigurationKey[]
 }
 
+export interface ChargingStationOptions extends JsonObject {
+  autoRegister?: boolean
+  autoStart?: boolean
+  enableStatistics?: boolean
+  ocppStrictCompliance?: boolean
+  persistentConfiguration?: boolean
+  stopTransactionsOnStopped?: boolean
+  supervisionUrls?: string | string[]
+}
+
 export interface ConfigurationKey extends OCPPConfigurationKey {
-  visible?: boolean
   reboot?: boolean
+  visible?: boolean
 }
 
-export interface OCPPConfigurationKey extends JsonObject {
-  key: string
-  readonly: boolean
-  value?: string
+export interface ConnectorStatus extends JsonObject {
+  authorizeIdTag?: string
+  availability: AvailabilityType
+  bootStatus?: ChargePointStatus
+  energyActiveImportRegisterValue?: number // In Wh
+  idTagAuthorized?: boolean
+  idTagLocalAuthorized?: boolean
+  localAuthorizeIdTag?: string
+  status?: ChargePointStatus
+  transactionEnergyActiveImportRegisterValue?: number // In Wh
+  transactionId?: number
+  transactionIdTag?: string
+  transactionRemoteStarted?: boolean
+  transactionStarted?: boolean
 }
 
-export enum OCPP16IncomingRequestCommand {
-  RESET = 'Reset',
-  CLEAR_CACHE = 'ClearCache',
-  CHANGE_AVAILABILITY = 'ChangeAvailability',
-  UNLOCK_CONNECTOR = 'UnlockConnector',
-  GET_CONFIGURATION = 'GetConfiguration',
-  CHANGE_CONFIGURATION = 'ChangeConfiguration',
-  SET_CHARGING_PROFILE = 'SetChargingProfile',
-  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
-  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
-  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
-  GET_DIAGNOSTICS = 'GetDiagnostics',
-  TRIGGER_MESSAGE = 'TriggerMessage'
+export interface EvseStatus extends JsonObject {
+  availability: AvailabilityType
+  connectors?: ConnectorStatus[]
 }
 
-export const IncomingRequestCommand = {
-  ...OCPP16IncomingRequestCommand
+export const FirmwareStatus = {
+  ...OCPP16FirmwareStatus,
 } as const
-export type IncomingRequestCommand = OCPP16IncomingRequestCommand
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type FirmwareStatus = OCPP16FirmwareStatus
 
-export enum OCPP16RequestCommand {
-  BOOT_NOTIFICATION = 'BootNotification',
-  HEARTBEAT = 'Heartbeat',
-  STATUS_NOTIFICATION = 'StatusNotification',
-  AUTHORIZE = 'Authorize',
-  START_TRANSACTION = 'StartTransaction',
-  STOP_TRANSACTION = 'StopTransaction',
-  METER_VALUES = 'MeterValues',
-  DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification'
+export interface FirmwareUpgrade extends JsonObject {
+  failureStatus?: FirmwareStatus
+  reset?: boolean
+  versionUpgrade?: {
+    patternGroup?: number
+    step?: number
+  }
 }
 
-export const RequestCommand = {
-  ...OCPP16RequestCommand
+export const IncomingRequestCommand = {
+  ...OCPP16IncomingRequestCommand,
 } as const
-export type RequestCommand = OCPP16RequestCommand
-
-export type BootNotificationResponse = OCPP16BootNotificationResponse
-
-export enum OCPP16RegistrationStatus {
-  ACCEPTED = 'Accepted',
-  PENDING = 'Pending',
-  REJECTED = 'Rejected'
-}
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type IncomingRequestCommand = OCPP16IncomingRequestCommand
 
 export interface OCPP16BootNotificationResponse extends JsonObject {
-  status: OCPP16RegistrationStatus
   currentTime: Date
   interval: number
+  status: OCPP16RegistrationStatus
 }
 
-export enum OCPP16MessageTrigger {
-  BootNotification = 'BootNotification',
-  DiagnosticsStatusNotification = 'DiagnosticsStatusNotification',
-  FirmwareStatusNotification = 'FirmwareStatusNotification',
-  Heartbeat = 'Heartbeat',
-  MeterValues = 'MeterValues',
-  StatusNotification = 'StatusNotification'
+export interface OCPPConfigurationKey extends JsonObject {
+  key: string
+  readonly: boolean
+  value?: string
 }
 
 export const MessageTrigger = {
-  ...OCPP16MessageTrigger
+  ...OCPP16MessageTrigger,
 } as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
 export type MessageTrigger = OCPP16MessageTrigger
 
-interface CommandsSupport extends JsonObject {
-  incomingCommands: Record<IncomingRequestCommand, boolean>
-  outgoingCommands?: Record<RequestCommand, boolean>
-}
-
-export enum OCPPVersion {
-  VERSION_16 = '1.6',
-  VERSION_20 = '2.0',
-  VERSION_201 = '2.0.1'
-}
-
-export enum OCPPProtocol {
-  JSON = 'json'
-}
-
-export enum CurrentType {
-  AC = 'AC',
-  DC = 'DC'
-}
-
-export enum Voltage {
-  VOLTAGE_110 = 110,
-  VOLTAGE_230 = 230,
-  VOLTAGE_400 = 400,
-  VOLTAGE_800 = 800
-}
-
-export enum AmpereUnits {
-  MILLI_AMPERE = 'mA',
-  CENTI_AMPERE = 'cA',
-  DECI_AMPERE = 'dA',
-  AMPERE = 'A'
-}
-
-export interface ConnectorStatus extends JsonObject {
-  availability: AvailabilityType
-  bootStatus?: ChargePointStatus
-  status?: ChargePointStatus
-  authorizeIdTag?: string
-  idTagAuthorized?: boolean
-  localAuthorizeIdTag?: string
-  idTagLocalAuthorized?: boolean
-  transactionRemoteStarted?: boolean
-  transactionStarted?: boolean
-  transactionId?: number
-  transactionIdTag?: string
-  energyActiveImportRegisterValue?: number // In Wh
-  transactionEnergyActiveImportRegisterValue?: number // In Wh
-}
-
-export interface EvseStatus extends JsonObject {
-  availability: AvailabilityType
-  connectors?: ConnectorStatus[]
-}
-
-export enum OCPP16AvailabilityType {
-  INOPERATIVE = 'Inoperative',
-  OPERATIVE = 'Operative'
-}
-export type AvailabilityType = OCPP16AvailabilityType
-
-export enum OCPP16ChargePointStatus {
-  AVAILABLE = 'Available',
-  PREPARING = 'Preparing',
-  CHARGING = 'Charging',
-  OCCUPIED = 'Occupied',
-  SUSPENDED_EVSE = 'SuspendedEVSE',
-  SUSPENDED_EV = 'SuspendedEV',
-  FINISHING = 'Finishing',
-  RESERVED = 'Reserved',
-  UNAVAILABLE = 'Unavailable',
-  FAULTED = 'Faulted'
-}
-export type ChargePointStatus = OCPP16ChargePointStatus
+export const RequestCommand = {
+  ...OCPP16RequestCommand,
+} as const
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export type RequestCommand = OCPP16RequestCommand
 
 export interface Status extends JsonObject {
-  start?: boolean
-  startDate?: Date
-  lastRunDate?: Date
-  stopDate?: Date
-  stoppedDate?: Date
-  authorizeRequests?: number
   acceptedAuthorizeRequests?: number
-  rejectedAuthorizeRequests?: number
-  startTransactionRequests?: number
   acceptedStartTransactionRequests?: number
-  rejectedStartTransactionRequests?: number
-  stopTransactionRequests?: number
   acceptedStopTransactionRequests?: number
+  authorizeRequests?: number
+  lastRunDate?: Date
+  rejectedAuthorizeRequests?: number
+  rejectedStartTransactionRequests?: number
   rejectedStopTransactionRequests?: number
   skippedConsecutiveTransactions?: number
   skippedTransactions?: number
+  start?: boolean
+  startDate?: Date
+  startTransactionRequests?: number
+  stopDate?: Date
+  stoppedDate?: Date
+  stopTransactionRequests?: number
+}
+
+interface CommandsSupport extends JsonObject {
+  incomingCommands: Record<IncomingRequestCommand, boolean>
+  outgoingCommands?: Record<RequestCommand, boolean>
 }
index 1c8de85bc20c623f1656104b01bebf1397f22407..40586918aeb13483e7bb002e9a9e7014aae27480 100644 (file)
@@ -5,16 +5,16 @@ export interface ConfigurationData {
 }
 
 export interface UIServerConfigurationSection {
-  name?: string
-  host: string
-  port: number
-  secure?: boolean
-  protocol: Protocol
-  version: ProtocolVersion
   authentication?: {
     enabled: boolean
+    password?: string
     type: AuthenticationType
     username?: string
-    password?: string
   }
+  host: string
+  name?: string
+  port: number
+  protocol: Protocol
+  secure?: boolean
+  version: ProtocolVersion
 }
index 8f597383a4bba8d98e727288dd987a579c1c0328..0cc5ae269da647da7a8df860a58b36e1cfa4ed07 100644 (file)
@@ -1,3 +1,3 @@
-type JsonPrimitive = string | number | boolean | Date | null
 export type JsonObject = { [key in string]?: JsonType }
-export type JsonType = JsonPrimitive | JsonType[] | JsonObject
+export type JsonType = JsonObject | JsonPrimitive | JsonType[]
+type JsonPrimitive = boolean | Date | null | number | string
index 8522aa11dcd8c4a10ac40a7e95ec94523bad1992..169a713e6357f509b5f217e436adda4e1e322ba0 100644 (file)
@@ -1,79 +1,79 @@
 import type { JsonObject } from './JsonType'
 
-export enum Protocol {
-  UI = 'ui'
-}
-
 export enum ApplicationProtocol {
   WS = 'ws',
-  WSS = 'wss'
-}
-
-export enum ProtocolVersion {
-  '0.0.1' = '0.0.1'
+  WSS = 'wss',
 }
 
 export enum AuthenticationType {
-  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth'
+  PROTOCOL_BASIC_AUTH = 'protocol-basic-auth',
 }
 
-export type ProtocolRequest = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ProcedureName,
-  RequestPayload
-]
-export type ProtocolResponse = [
-  `${string}-${string}-${string}-${string}-${string}`,
-  ResponsePayload
-]
-
-export type ProtocolRequestHandler = (
-  payload: RequestPayload
-) => ResponsePayload | Promise<ResponsePayload>
-
 export enum ProcedureName {
-  SIMULATOR_STATE = 'simulatorState',
-  START_SIMULATOR = 'startSimulator',
-  STOP_SIMULATOR = 'stopSimulator',
-  LIST_TEMPLATES = 'listTemplates',
-  LIST_CHARGING_STATIONS = 'listChargingStations',
   ADD_CHARGING_STATIONS = 'addChargingStations',
+  CLOSE_CONNECTION = 'closeConnection',
   DELETE_CHARGING_STATIONS = 'deleteChargingStations',
-  SET_SUPERVISION_URL = 'setSupervisionUrl',
-  START_CHARGING_STATION = 'startChargingStation',
-  STOP_CHARGING_STATION = 'stopChargingStation',
+  LIST_CHARGING_STATIONS = 'listChargingStations',
+  LIST_TEMPLATES = 'listTemplates',
   OPEN_CONNECTION = 'openConnection',
-  CLOSE_CONNECTION = 'closeConnection',
+  SET_SUPERVISION_URL = 'setSupervisionUrl',
+  SIMULATOR_STATE = 'simulatorState',
   START_AUTOMATIC_TRANSACTION_GENERATOR = 'startAutomaticTransactionGenerator',
-  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
+  START_CHARGING_STATION = 'startChargingStation',
+  START_SIMULATOR = 'startSimulator',
   START_TRANSACTION = 'startTransaction',
-  STOP_TRANSACTION = 'stopTransaction'
+  STOP_AUTOMATIC_TRANSACTION_GENERATOR = 'stopAutomaticTransactionGenerator',
+  STOP_CHARGING_STATION = 'stopChargingStation',
+  STOP_SIMULATOR = 'stopSimulator',
+  STOP_TRANSACTION = 'stopTransaction',
 }
 
-export interface RequestPayload extends JsonObject {
-  hashIds?: string[]
-  connectorIds?: number[]
+export enum Protocol {
+  UI = 'ui',
 }
 
+export enum ProtocolVersion {
+  '0.0.1' = '0.0.1',
+}
 export enum ResponseStatus {
+  FAILURE = 'failure',
   SUCCESS = 'success',
-  FAILURE = 'failure'
 }
 
-export interface ResponsePayload extends JsonObject {
-  status: ResponseStatus
+export type ProtocolRequest = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ProcedureName,
+  RequestPayload
+]
+
+export type ProtocolRequestHandler = (
+  payload: RequestPayload
+) => Promise<ResponsePayload> | ResponsePayload
+
+export type ProtocolResponse = [
+  `${string}-${string}-${string}-${string}-${string}`,
+  ResponsePayload
+]
+
+export interface RequestPayload extends JsonObject {
+  connectorIds?: number[]
   hashIds?: string[]
 }
 
-interface TemplateStatistics extends JsonObject {
-  configured: number
-  added: number
-  started: number
-  indexes: number[]
+export interface ResponsePayload extends JsonObject {
+  hashIds?: string[]
+  status: ResponseStatus
 }
 
 export interface SimulatorState extends JsonObject {
-  version: string
   started: boolean
   templateStatistics: Record<string, TemplateStatistics>
+  version: string
+}
+
+interface TemplateStatistics extends JsonObject {
+  added: number
+  configured: number
+  indexes: number[]
+  started: number
 }
index d730f0d634160a7642660d90251e0c4fdae6d198..b9159b18ce1a42925b3c14d44a7f3d6bedd79f6f 100644 (file)
@@ -3,7 +3,7 @@ export type {
   ChargingStationInfo,
   ChargingStationOptions,
   ConnectorStatus,
-  Status
+  Status,
 } from './ChargingStationType'
 export type { ConfigurationData, UIServerConfigurationSection } from './ConfigurationType'
 export {
@@ -16,5 +16,5 @@ export {
   type RequestPayload,
   type ResponsePayload,
   ResponseStatus,
-  type SimulatorState
+  type SimulatorState,
 } from './UIProtocol'
index 695b88608b4df60c0e90ff848167057ddcbcdfb7..88e4e658e05b181e579627ba7d3a88ae754847d3 100644 (file)
               if (
                 getFromLocalStorage<number>('uiServerConfigurationIndex', 0) !== state.uiServerIndex
               ) {
-                $uiClient.setConfiguration(
-                  ($configuration.value.uiServer as UIServerConfigurationSection[])[
+                $uiClient?.setConfiguration(
+                  ($configuration!.value.uiServer as UIServerConfigurationSection[])[
                     state.uiServerIndex
                   ]
                 )
                 registerWSEventListeners()
-                $uiClient.registerWSEventListener(
+                $uiClient?.registerWSEventListener(
                   'open',
                   () => {
                     setToLocalStorage<number>('uiServerConfigurationIndex', state.uiServerIndex)
                     clearToggleButtons()
+                    refresh()
                     $route.name !== 'charging-stations' &&
                       $router.push({ name: 'charging-stations' })
                   },
                   { once: true }
                 )
-                $uiClient.registerWSEventListener(
+                $uiClient?.registerWSEventListener(
                   'error',
                   () => {
                     state.uiServerIndex = getFromLocalStorage<number>(
                       'uiServerConfigurationIndex',
                       0
                     )
-                    $uiClient.setConfiguration(
-                      ($configuration.value.uiServer as UIServerConfigurationSection[])[
+                    $uiClient?.setConfiguration(
+                      ($configuration!.value.uiServer as UIServerConfigurationSection[])[
                         getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
                       ]
                     )
       <ToggleButton
         :id="'simulator'"
         :key="state.renderSimulator"
-        :status="simulatorState?.started"
-        :on="() => startSimulator()"
-        :off="() => stopSimulator()"
         :class="simulatorButtonClass"
+        :off="() => stopSimulator()"
+        :on="() => startSimulator()"
+        :status="simulatorState?.started"
       >
         {{ simulatorButtonMessage }}
       </ToggleButton>
       <ToggleButton
         :id="'add-charging-stations'"
         :key="state.renderAddChargingStations"
-        :shared="true"
-        :on="
+        :off="
           () => {
-            $router.push({ name: 'add-charging-stations' })
+            $router.push({ name: 'charging-stations' })
           }
         "
-        :off="
+        :on="
           () => {
-            $router.push({ name: 'charging-stations' })
+            $router.push({ name: 'add-charging-stations' })
           }
         "
+        :shared="true"
         @clicked="
           () => {
             state.renderChargingStations = randomUUID()
       />
     </Container>
     <CSTable
-      v-show="Array.isArray($chargingStations.value) && $chargingStations.value.length > 0"
+      v-show="Array.isArray($chargingStations?.value) && $chargingStations.value.length > 0"
       :key="state.renderChargingStations"
-      :charging-stations="$chargingStations.value"
+      :charging-stations="$chargingStations!.value"
       @need-refresh="
         () => {
           state.renderAddChargingStations = randomUUID()
 import { computed, getCurrentInstance, onMounted, onUnmounted, ref, watch } from 'vue'
 import { useToast } from 'vue-toast-notification'
 
+import type {
+  ChargingStationData,
+  ResponsePayload,
+  SimulatorState,
+  UIServerConfigurationSection,
+} from '@/types'
+
 import ReloadButton from '@/components/buttons/ReloadButton.vue'
 import ToggleButton from '@/components/buttons/ToggleButton.vue'
 import CSTable from '@/components/charging-stations/CSTable.vue'
@@ -126,14 +134,8 @@ import {
   getLocalStorage,
   randomUUID,
   setToLocalStorage,
-  useUIClient
+  useUIClient,
 } from '@/composables'
-import type {
-  ChargingStationData,
-  ResponsePayload,
-  SimulatorState,
-  UIServerConfigurationSection
-} from '@/types'
 
 const simulatorState = ref<SimulatorState | undefined>(undefined)
 
@@ -142,40 +144,45 @@ const simulatorButtonClass = computed<string>(() =>
 )
 const simulatorButtonMessage = computed<string>(
   () =>
-    `${simulatorState.value?.started === true ? 'Stop' : 'Start'} Simulator${simulatorState.value?.version != null ? ` (${simulatorState.value.version})` : ''}`
+    `${simulatorState.value?.started === true ? 'Stop' : 'Start'} Simulator${
+      simulatorState.value?.version != null ? ` (${simulatorState.value.version})` : ''
+    }`
 )
 
 const state = ref<{
-  renderSimulator: `${string}-${string}-${string}-${string}-${string}`
-  renderAddChargingStations: `${string}-${string}-${string}-${string}-${string}`
-  renderChargingStations: `${string}-${string}-${string}-${string}-${string}`
+  gettingChargingStations: boolean
   gettingSimulatorState: boolean
   gettingTemplates: boolean
-  gettingChargingStations: boolean
+  renderAddChargingStations: `${string}-${string}-${string}-${string}-${string}`
+  renderChargingStations: `${string}-${string}-${string}-${string}-${string}`
+  renderSimulator: `${string}-${string}-${string}-${string}-${string}`
   uiServerIndex: number
 }>({
-  renderSimulator: randomUUID(),
-  renderAddChargingStations: randomUUID(),
-  renderChargingStations: randomUUID(),
+  gettingChargingStations: false,
   gettingSimulatorState: false,
   gettingTemplates: false,
-  gettingChargingStations: false,
-  uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
+  renderAddChargingStations: randomUUID(),
+  renderChargingStations: randomUUID(),
+  renderSimulator: randomUUID(),
+  uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0),
 })
 
+const refresh = (): void => {
+  state.value.renderChargingStations = randomUUID()
+  state.value.renderAddChargingStations = randomUUID()
+}
+
 const clearToggleButtons = (): void => {
   for (const key in getLocalStorage()) {
     if (key.includes('toggle-button')) {
       deleteFromLocalStorage(key)
     }
   }
-  state.value.renderChargingStations = randomUUID()
-  state.value.renderAddChargingStations = randomUUID()
 }
 
 const app = getCurrentInstance()
 
-watch(app!.appContext.config.globalProperties.$chargingStations, () => {
+watch(app!.appContext.config.globalProperties!.$chargingStations, () => {
   state.value.renderChargingStations = randomUUID()
 })
 
@@ -185,13 +192,13 @@ watch(simulatorState, () => {
 
 const clearTemplates = (): void => {
   if (app != null) {
-    app.appContext.config.globalProperties.$templates.value = []
+    app.appContext.config.globalProperties.$templates!.value = []
   }
 }
 
 const clearChargingStations = (): void => {
   if (app != null) {
-    app.appContext.config.globalProperties.$chargingStations.value = []
+    app.appContext.config.globalProperties.$chargingStations!.value = []
   }
 }
 
@@ -206,14 +213,15 @@ const getSimulatorState = (): void => {
       .simulatorState()
       .then((response: ResponsePayload) => {
         simulatorState.value = response.state as SimulatorState
+        return undefined
+      })
+      .finally(() => {
+        state.value.gettingSimulatorState = false
       })
       .catch((error: Error) => {
         $toast.error('Error at fetching simulator state')
         console.error('Error at fetching simulator state:', error)
       })
-      .finally(() => {
-        state.value.gettingSimulatorState = false
-      })
   }
 }
 
@@ -224,17 +232,18 @@ const getTemplates = (): void => {
       .listTemplates()
       .then((response: ResponsePayload) => {
         if (app != null) {
-          app.appContext.config.globalProperties.$templates.value = response.templates as string[]
+          app.appContext.config.globalProperties.$templates!.value = response.templates as string[]
         }
+        return undefined
+      })
+      .finally(() => {
+        state.value.gettingTemplates = false
       })
       .catch((error: Error) => {
         clearTemplates()
         $toast.error('Error at fetching charging station templates')
         console.error('Error at fetching charging station templates:', error)
       })
-      .finally(() => {
-        state.value.gettingTemplates = false
-      })
   }
 }
 
@@ -245,18 +254,19 @@ const getChargingStations = (): void => {
       .listChargingStations()
       .then((response: ResponsePayload) => {
         if (app != null) {
-          app.appContext.config.globalProperties.$chargingStations.value =
+          app.appContext.config.globalProperties.$chargingStations!.value =
             response.chargingStations as ChargingStationData[]
         }
+        return undefined
+      })
+      .finally(() => {
+        state.value.gettingChargingStations = false
       })
       .catch((error: Error) => {
         clearChargingStations()
         $toast.error('Error at fetching charging stations')
         console.error('Error at fetching charging stations:', error)
       })
-      .finally(() => {
-        state.value.gettingChargingStations = false
-      })
   }
 }
 
@@ -286,42 +296,45 @@ onUnmounted(() => {
   unregisterWSEventListeners()
 })
 
-const uiServerConfigurations: { index: number; configuration: UIServerConfigurationSection }[] = (
-  app?.appContext.config.globalProperties.$configuration.value
+const uiServerConfigurations: {
+  configuration: UIServerConfigurationSection
+  index: number
+}[] = (
+  app!.appContext.config.globalProperties.$configuration!.value
     .uiServer as UIServerConfigurationSection[]
 ).map((configuration: UIServerConfigurationSection, index: number) => ({
+  configuration,
   index,
-  configuration
 }))
 
 const startSimulator = (): void => {
   uiClient
     .startSimulator()
     .then(() => {
-      $toast.success('Simulator successfully started')
+      return $toast.success('Simulator successfully started')
+    })
+    .finally(() => {
+      getSimulatorState()
     })
     .catch((error: Error) => {
       $toast.error('Error at starting simulator')
       console.error('Error at starting simulator:', error)
     })
-    .finally(() => {
-      getSimulatorState()
-    })
 }
 const stopSimulator = (): void => {
   uiClient
     .stopSimulator()
     .then(() => {
       clearChargingStations()
-      $toast.success('Simulator successfully stopped')
+      return $toast.success('Simulator successfully stopped')
+    })
+    .finally(() => {
+      getSimulatorState()
     })
     .catch((error: Error) => {
       $toast.error('Error at stopping simulator')
       console.error('Error at stopping simulator:', error)
     })
-    .finally(() => {
-      getSimulatorState()
-    })
 }
 </script>
 
index a87072aa84439d5a4ca6fc5818c314187f9e9235..7f800e80fd5bce248f73b37b249879a1aa8f9487 100644 (file)
@@ -1,4 +1,8 @@
-<template><Container id="not-found">404 - Not found</Container></template>
+<template>
+  <Container id="not-found">
+    404 - Not found
+  </Container>
+</template>
 
 <script setup lang="ts">
 import Container from '@/components/Container.vue'
index 46a8bb75884f67da0c4b2ecfc9138bff44fb7ab2..d11b339dbfe77f7482b4b0c144d9d77974fd314c 100644 (file)
@@ -1,13 +1,12 @@
+import finalhandler from 'finalhandler'
 import { createServer } from 'node:http'
 import { dirname, join } from 'node:path'
 import { env } from 'node:process'
 import { fileURLToPath } from 'node:url'
-
-import finalhandler from 'finalhandler'
 import serveStatic from 'serve-static'
 
 const isCFEnvironment = env.VCAP_APPLICATION != null
-const PORT = isCFEnvironment ? parseInt(env.PORT) : 3030
+const PORT = isCFEnvironment ? Number.parseInt(env.PORT) : 3030
 const uiPath = join(dirname(fileURLToPath(import.meta.url)), './dist')
 
 const serve = serveStatic(uiPath)
index 8a417af8619544233f04c94705de0e4738c8d641..ba8c8c258aa14a2198acc4cebbd9a918a9f36dbb 100644 (file)
@@ -1,13 +1,14 @@
 import { shallowMount } from '@vue/test-utils'
 import { expect, test } from 'vitest'
 
-import CSTable from '@/components/charging-stations/CSTable.vue'
 import type { ChargingStationData } from '@/types'
 
+import CSTable from '@/components/charging-stations/CSTable.vue'
+
 test('renders CS table columns name', () => {
   const chargingStations: ChargingStationData[] = []
   const wrapper = shallowMount(CSTable, {
-    props: { chargingStations, idTag: '0' }
+    props: { chargingStations, idTag: '0' },
   })
   expect(wrapper.text()).to.include('Name')
   expect(wrapper.text()).to.include('Started')
index 4c48e296d56b106fa7de2395378261f44686fe5c..d27bb51311370acf841f036cd92f1507496be805 100644 (file)
@@ -1,9 +1,10 @@
 {
   "$schema": "https://json.schemastore.org/tsconfig",
-  "extends": ["@tsconfig/node20/tsconfig.json", "@vue/tsconfig/tsconfig.dom.json"],
+  "extends": ["@tsconfig/node22/tsconfig.json", "@vue/tsconfig/tsconfig.dom.json"],
   "compilerOptions": {
     "experimentalDecorators": true,
     "allowSyntheticDefaultImports": true,
+    "noImplicitOverride": true,
     "sourceMap": true,
     "composite": true,
     "baseUrl": "./",
@@ -12,6 +13,5 @@
       "@/*": ["./src/*"]
     }
   },
-  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
-  "exclude": ["node_modules"]
+  "include": ["*.ts", "src/**/*.ts", "src/**/*.vue", "tests/**/*.ts"]
 }
index 6d5f08a34a04b179e7e53e5e3d57dc33caf0f3cb..100a6db3dda2944760e47a02ef53b92e75ec9f07 100644 (file)
@@ -1,14 +1,13 @@
-import { fileURLToPath, URL } from 'node:url'
-
 import vue from '@vitejs/plugin-vue'
 import vueJsx from '@vitejs/plugin-vue-jsx'
+import { fileURLToPath, URL } from 'node:url'
 import { defineConfig } from 'vite'
 
 export default defineConfig({
   plugins: [vue(), vueJsx()],
   resolve: {
     alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  }
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
+    },
+  },
 })
index c5276e5caa0b040e1b00d0d8de6e5e3b6f53335b..891e3761ad2a39f6fb69d49df5fdabcf395e7de3 100644 (file)
@@ -1,5 +1,4 @@
 import { fileURLToPath } from 'node:url'
-
 import { mergeConfig } from 'vite'
 import { configDefaults, defineConfig } from 'vitest/config'
 
@@ -9,13 +8,13 @@ export default mergeConfig(
   viteConfig,
   defineConfig({
     test: {
+      coverage: {
+        provider: 'v8',
+        reporter: ['text', 'lcov'],
+      },
       environment: 'jsdom',
       exclude: [...configDefaults.exclude, 'e2e/*'],
       root: fileURLToPath(new URL('./', import.meta.url)),
-      coverage: {
-        provider: 'v8',
-        reporter: ['text', 'lcov']
-      }
-    }
+    },
   })
 )