diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 91462f325..74460716f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,9 @@ # Description diff --git a/.github/actions/clp-core-build/action.yaml b/.github/actions/clp-core-build/action.yaml index aed244c25..e627ec4fb 100644 --- a/.github/actions/clp-core-build/action.yaml +++ b/.github/actions/clp-core-build/action.yaml @@ -8,6 +8,9 @@ inputs: use_published_image: description: "Whether to use the published container image" required: true + use_shared_libs: + description: "Whether to build the core binaries by linking against shared libraries" + required: true upload_binaries: description: "Whether to upload the core binaries" required: true @@ -18,10 +21,6 @@ inputs: runs: using: "composite" steps: - - shell: "bash" - working-directory: "./components/core" - run: "./tools/scripts/deps-download/download-all.sh" - - if: "inputs.use_published_image == 'false'" uses: "actions/download-artifact@v4" with: @@ -46,13 +45,21 @@ runs: fi shell: "bash" + - run: "./tools/scripts/deps-download/init.sh" + shell: "bash" + - run: >- docker run --user $(id -u):$(id -g) - --volume "$GITHUB_WORKSPACE/components/core":/mnt/clp + --volume "$GITHUB_WORKSPACE":/mnt/clp --workdir /mnt/clp ${{steps.get_image_props.outputs.qualified_image_name}} - /mnt/clp/tools/scripts/utils/build-and-run-unit-tests.sh . build + bash -c "task deps:core && + python3 /mnt/clp/components/core/tools/scripts/utils/build-and-run-unit-tests.py + ${{inputs.use_shared_libs == 'true' && '--use-shared-libs' || ''}} + --source-dir /mnt/clp/components/core + --build-dir /mnt/clp/components/core/build + --num-jobs $(getconf _NPROCESSORS_ONLN)" shell: "bash" - if: "inputs.upload_binaries == 'true'" diff --git a/.github/workflows/clp-core-build-macos.yaml b/.github/workflows/clp-core-build-macos.yaml index c46dfc878..8196e75d8 100644 --- a/.github/workflows/clp-core-build-macos.yaml +++ b/.github/workflows/clp-core-build-macos.yaml @@ -8,9 +8,12 @@ on: - "components/core/CMakeLists.txt" - "components/core/src/**" - "components/core/tests/**" - - "components/core/tools/scripts/lib_install/macos-12/**" + - "components/core/tools/scripts/lib_install/macos/**" - "components/core/tools/scripts/deps-download/**" - - "components/core/tools/scripts/utils/build-and-run-unit-tests.sh" + - "components/core/tools/scripts/utils/build-and-run-unit-tests.py" + - "deps-tasks.yml" + - "Taskfile.yml" + - "tools/scripts/deps-download/**" push: paths: - ".github/workflows/clp-core-build-macos.yaml" @@ -18,9 +21,15 @@ on: - "components/core/CMakeLists.txt" - "components/core/src/**" - "components/core/tests/**" - - "components/core/tools/scripts/lib_install/macos-12/**" + - "components/core/tools/scripts/lib_install/macos/**" - "components/core/tools/scripts/deps-download/**" - - "components/core/tools/scripts/utils/build-and-run-unit-tests.sh" + - "components/core/tools/scripts/utils/build-and-run-unit-tests.py" + - "deps-tasks.yml" + - "Taskfile.yml" + - "tools/scripts/deps-download/**" + schedule: + # Run daily at 00:15 UTC (the 15 is to avoid periods of high load) + - cron: "15 0 * * *" workflow_dispatch: concurrency: @@ -30,7 +39,11 @@ concurrency: jobs: build-macos: - runs-on: "macos-12" + strategy: + matrix: + runner: ["macos-13", "macos-14"] + use_shared_libs: [true, false] + runs-on: "${{matrix.runner}}" steps: - uses: "actions/checkout@v4" with: @@ -40,20 +53,28 @@ jobs: - name: "Remove preinstalled binaries which conflict with brew's installs" run: | rm -f /usr/local/bin/2to3* + rm -f /usr/local/bin/go* rm -f /usr/local/bin/idle3* rm -f /usr/local/bin/pydoc3* rm -f /usr/local/bin/python3* - name: "Install dependencies" - run: "./components/core/tools/scripts/lib_install/macos-12/install-all.sh" + run: "./components/core/tools/scripts/lib_install/macos/install-all.sh" - - name: "Download source dependencies" + - run: "./tools/scripts/deps-download/init.sh" + shell: "bash" + + - run: "task deps:core" shell: "bash" - working-directory: "./components/core" - run: "./tools/scripts/deps-download/download-all.sh" - name: "Build CLP-core and run unit tests" shell: "bash" working-directory: "./components/core" # NOTE: We omit the Stopwatch tests since GH's macOS runner is too slow - run: "./tools/scripts/utils/build-and-run-unit-tests.sh . build ~[Stopwatch]" + run: >- + python3 ./tools/scripts/utils/build-and-run-unit-tests.py + ${{matrix.use_shared_libs == 'true' && '--use-shared-libs' || ''}} + --source-dir . + --build-dir build + --num-jobs $(getconf _NPROCESSORS_ONLN) + --test-spec "~[Stopwatch]" diff --git a/.github/workflows/clp-core-build.yaml b/.github/workflows/clp-core-build.yaml index d0dbbb28a..20b305f8d 100644 --- a/.github/workflows/clp-core-build.yaml +++ b/.github/workflows/clp-core-build.yaml @@ -8,7 +8,10 @@ on: - ".github/workflows/clp-core-build.yaml" - ".gitmodules" - "components/core/**" - - "!components/core/tools/scripts/lib_install/macos-12/**" + - "deps-tasks.yml" + - "Taskfile.yml" + - "tools/scripts/deps-download/**" + - "!components/core/tools/scripts/lib_install/macos/**" push: paths: - ".github/actions/clp-core-build/action.yaml" @@ -16,7 +19,13 @@ on: - ".github/workflows/clp-core-build.yaml" - ".gitmodules" - "components/core/**" - - "!components/core/tools/scripts/lib_install/macos-12/**" + - "deps-tasks.yml" + - "Taskfile.yml" + - "tools/scripts/deps-download/**" + - "!components/core/tools/scripts/lib_install/macos/**" + schedule: + # Run daily at 00:15 UTC (the 15 is to avoid periods of high load) + - cron: "15 0 * * *" workflow_dispatch: env: @@ -30,7 +39,7 @@ jobs: filter-relevant-changes: runs-on: "ubuntu-latest" outputs: - centos74_image_changed: "${{steps.filter.outputs.centos74_image}}" + centos_stream_9_image_changed: "${{steps.filter.outputs.centos_stream_9_image}}" ubuntu_focal_image_changed: "${{steps.filter.outputs.ubuntu_focal_image}}" ubuntu_jammy_image_changed: "${{steps.filter.outputs.ubuntu_jammy_image}}" clp_changed: "${{steps.filter.outputs.clp}}" @@ -57,12 +66,12 @@ jobs: # image (since it would be different from the published image). base: "main" filters: | - centos74_image: + centos_stream_9_image: - ".github/actions/**" - ".github/workflows/clp-core-build.yaml" - "components/core/tools/scripts/lib_install/*.sh" - - "components/core/tools/docker-images/clp-env-base-centos7.4/**" - - "components/core/tools/scripts/lib_install/centos7.4/**" + - "components/core/tools/docker-images/clp-env-base-centos-stream-9/**" + - "components/core/tools/scripts/lib_install/centos-stream-9/**" ubuntu_focal_image: - ".github/actions/**" - ".github/workflows/clp-core-build.yaml" @@ -83,11 +92,13 @@ jobs: - "components/core/CMakeLists.txt" - "components/core/src/**" - "components/core/tests/**" - - "components/core/tools/scripts/deps-download/**" - - "components/core/tools/scripts/utils/build-and-run-unit-tests.sh" + - "components/core/tools/scripts/utils/build-and-run-unit-tests.py" + - "deps-tasks.yml" + - "Taskfile.yml" + - "tools/scripts/deps-download/**" - centos74-deps-image: - if: "needs.filter-relevant-changes.outputs.centos74_image_changed == 'true'" + centos-stream-9-deps-image: + if: "needs.filter-relevant-changes.outputs.centos_stream_9_image_changed == 'true'" needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: @@ -101,7 +112,7 @@ jobs: - uses: "./.github/actions/clp-core-build-containers" env: - OS_NAME: "centos7.4" + OS_NAME: "centos-stream-9" with: image_name: "${{env.DEPS_IMAGE_NAME_PREFIX}}${{env.OS_NAME}}" docker_context: "components/core" @@ -161,14 +172,19 @@ jobs: ${{github.event_name != 'pull_request' && github.ref == 'refs/heads/main'}} token: "${{secrets.GITHUB_TOKEN}}" - centos74-binaries: + centos-stream-9-binaries: # Run if the ancestor jobs succeeded OR they were skipped and clp was changed. if: >- success() || (!cancelled() && !failure() && needs.filter-relevant-changes.outputs.clp_changed == 'true') needs: - - "centos74-deps-image" + - "centos-stream-9-deps-image" - "filter-relevant-changes" + strategy: + matrix: + use_shared_libs: [true, false] + name: "centos-stream-9-${{matrix.use_shared_libs && 'dynamic' || 'static'}}-linked-bins" + continue-on-error: true runs-on: "ubuntu-latest" steps: - uses: "actions/checkout@v4" @@ -181,11 +197,12 @@ jobs: - uses: "./.github/actions/clp-core-build" env: - OS_NAME: "centos7.4" + OS_NAME: "centos-stream-9" with: image_name: "${{env.DEPS_IMAGE_NAME_PREFIX}}${{env.OS_NAME}}" + use_shared_libs: "${{matrix.use_shared_libs}}" use_published_image: >- - ${{needs.filter-relevant-changes.outputs.centos74_image_changed == 'false' + ${{needs.filter-relevant-changes.outputs.centos_stream_9_image_changed == 'false' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/main')}} upload_binaries: "false" @@ -197,6 +214,15 @@ jobs: needs: - "filter-relevant-changes" - "ubuntu-focal-deps-image" + strategy: + matrix: + include: + - use_shared_libs: true + upload_binaries: false + - use_shared_libs: false + upload_binaries: true + name: "ubuntu-focal-${{matrix.use_shared_libs && 'dynamic' || 'static'}}-linked-bins" + continue-on-error: true runs-on: "ubuntu-latest" steps: - uses: "actions/checkout@v4" @@ -212,10 +238,11 @@ jobs: OS_NAME: "ubuntu-focal" with: image_name: "${{env.DEPS_IMAGE_NAME_PREFIX}}${{env.OS_NAME}}" + use_shared_libs: "${{matrix.use_shared_libs}}" use_published_image: >- ${{needs.filter-relevant-changes.outputs.ubuntu_focal_image_changed == 'false' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/main')}} - upload_binaries: "true" + upload_binaries: "${{matrix.upload_binaries}}" binaries_artifact_name: "${{env.BINARIES_ARTIFACT_NAME_PREFIX}}${{env.OS_NAME}}" ubuntu-jammy-binaries: @@ -226,6 +253,11 @@ jobs: needs: - "filter-relevant-changes" - "ubuntu-jammy-deps-image" + strategy: + matrix: + use_shared_libs: [true, false] + name: "ubuntu-jammy-${{matrix.use_shared_libs && 'dynamic' || 'static'}}-linked-bins" + continue-on-error: true runs-on: "ubuntu-latest" steps: - uses: "actions/checkout@v4" @@ -241,6 +273,7 @@ jobs: OS_NAME: "ubuntu-jammy" with: image_name: "${{env.DEPS_IMAGE_NAME_PREFIX}}${{env.OS_NAME}}" + use_shared_libs: "${{matrix.use_shared_libs}}" use_published_image: >- ${{needs.filter-relevant-changes.outputs.ubuntu_jammy_image_changed == 'false' || (github.event_name != 'pull_request' && github.ref == 'refs/heads/main')}} diff --git a/.github/workflows/clp-docs.yaml b/.github/workflows/clp-docs.yaml new file mode 100644 index 000000000..38e4cb172 --- /dev/null +++ b/.github/workflows/clp-docs.yaml @@ -0,0 +1,41 @@ +name: "clp-docs" + +on: + pull_request: + push: + schedule: + # Run daily at 00:15 UTC (the 15 is to avoid periods of high load) + - cron: "15 0 * * *" + workflow_dispatch: + +concurrency: + group: "${{github.workflow}}-${{github.ref}}" + # Cancel in-progress jobs for efficiency + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + os: ["macos-latest", "ubuntu-latest"] + runs-on: "${{matrix.os}}" + steps: + - uses: "actions/checkout@v4" + with: + submodules: "recursive" + + - uses: "actions/setup-python@v5" + with: + python-version: "3.10" + + - name: "Install task" + shell: "bash" + run: "npm install -g @go-task/cli" + + - if: "matrix.os == 'macos-latest'" + name: "Install coreutils (for md5sum)" + run: "brew install coreutils" + + - name: "Build docs" + shell: "bash" + run: "task docs:site" diff --git a/.github/workflows/clp-execution-image-build.yaml b/.github/workflows/clp-execution-image-build.yaml index d0bc5b017..058e23d5f 100644 --- a/.github/workflows/clp-execution-image-build.yaml +++ b/.github/workflows/clp-execution-image-build.yaml @@ -11,6 +11,9 @@ on: - ".github/actions/clp-execution-image-build/action.yaml" - ".github/workflows/clp-execution-image-build.yaml" - "tools/docker-images/**/*" + schedule: + # Run daily at 00:15 UTC (the 15 is to avoid periods of high load) + - cron: "15 0 * * *" workflow_dispatch: concurrency: diff --git a/.github/workflows/clp-lint.yaml b/.github/workflows/clp-lint.yaml index 75f74fe4a..bbe485c5d 100644 --- a/.github/workflows/clp-lint.yaml +++ b/.github/workflows/clp-lint.yaml @@ -4,7 +4,7 @@ on: pull_request: push: schedule: - # Run at midnight UTC every day with 15 minutes delay added to avoid high load periods + # Run daily at 00:15 UTC (the 15 is to avoid periods of high load) - cron: "15 0 * * *" workflow_dispatch: diff --git a/.github/workflows/clp-pr-title-checks.yaml b/.github/workflows/clp-pr-title-checks.yaml new file mode 100644 index 000000000..428e9f21d --- /dev/null +++ b/.github/workflows/clp-pr-title-checks.yaml @@ -0,0 +1,23 @@ +name: "clp-pr-title-checks" + +on: + pull_request_target: + types: ["edited", "opened", "reopened"] + branches: ["main"] + +concurrency: + group: "${{github.workflow}}-${{github.ref}}" + + # Cancel in-progress jobs for efficiency + cancel-in-progress: true + +jobs: + conventional-commits: + permissions: + # For amannn/action-semantic-pull-request + pull-requests: "read" + runs-on: "ubuntu-latest" + steps: + - uses: "amannn/action-semantic-pull-request@v5" + env: + GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}" diff --git a/.gitmodules b/.gitmodules index 614f0871e..3935b0101 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,12 +14,18 @@ [submodule "components/core/submodules/log-surgeon"] path = components/core/submodules/log-surgeon url = https://github.com/y-scope/log-surgeon.git -[submodule "components/core/submodules/boost-outcome"] - path = components/core/submodules/boost-outcome - url = https://github.com/boostorg/outcome.git [submodule "components/core/submodules/simdjson"] path = components/core/submodules/simdjson url = https://github.com/simdjson/simdjson.git [submodule "components/core/submodules/abseil-cpp"] path = components/core/submodules/abseil-cpp url = https://github.com/abseil/abseil-cpp.git +[submodule "tools/yscope-dev-utils"] + path = tools/yscope-dev-utils + url = https://github.com/y-scope/yscope-dev-utils.git +[submodule "components/core/submodules/outcome"] + path = components/core/submodules/outcome + url = https://github.com/ned14/outcome.git +[submodule "components/log-viewer-webui/yscope-log-viewer"] + path = components/log-viewer-webui/yscope-log-viewer + url = https://github.com/y-scope/yscope-log-viewer.git diff --git a/README.md b/README.md index 5d8d00f38..4a5c4a81d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ YScope's Compressed Log Processor (CLP) compresses your logs, and allows you to compressed logs without decompression. CLP supports both JSON logs and unstructured (i.e., free text) logs. It also supports real-time log compression within several logging libraries. CLP also includes purpose-built web interfaces for searching and viewing the compressed logs. To learn more -about it, you can read our [paper][clp-paper]. +about it, read our [2021 paper][clp-paper-21] about handling unstructured logs and our +[2024 paper][clp-paper-24] on extending it to JSON logs. # Benchmarks @@ -27,7 +28,7 @@ index-less design, so for a fair comparison, we disabled MongoDB and PostgreSQL' left them enabled, MongoDB and PostgreSQL's compression ratio would be worse. We didn't disable indexing for Elasticsearch or Splunk since these tools are fundamentally index-based (i.e., logs cannot be searched without indexes). More details about our experimental methodology can be found in -the [CLP paper][clp-paper]. +the [2021 paper][clp-paper-21] and the [2024 paper][clp-paper-24]. # System Overview @@ -94,7 +95,8 @@ If you would like a feature or want to report a bug, please file an issue and we [clp-ffi-go]: https://github.com/y-scope/clp-ffi-go [clp-ffi-py]: https://github.com/y-scope/clp-ffi-py [clp-loglib-py]: https://github.com/y-scope/clp-loglib-py -[clp-paper]: https://www.usenix.org/system/files/osdi21-rodrigues.pdf +[clp-paper-21]: https://www.usenix.org/system/files/osdi21-rodrigues.pdf +[clp-paper-24]: https://www.usenix.org/system/files/osdi24-wang-rui.pdf [core]: http://docs.yscope.com/clp/main/dev-guide/components-core [core-container]: http://docs.yscope.com/clp/main/user-guide/core-container [datasets]: https://docs.yscope.com/clp/main/user-guide/resources-datasets diff --git a/Taskfile.yml b/Taskfile.yml index 256898d55..5c20ef460 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,19 +1,33 @@ version: "3" includes: + deps: "deps-tasks.yml" docs: "docs/tasks.yml" lint: "lint-tasks.yml" + utils: "tools/yscope-dev-utils/taskfiles/utils.yml" vars: - # Paths + # Source paths + G_COMPONENTS_DIR: "{{.ROOT_DIR}}/components" + G_CORE_COMPONENT_DIR: "{{.G_COMPONENTS_DIR}}/core" + G_CORE_COMPONENT_SUBMODULES_DIR: "{{.G_CORE_COMPONENT_DIR}}/submodules" + G_LOG_VIEWER_WEBUI_SRC_DIR: "{{.G_COMPONENTS_DIR}}/log-viewer-webui" + + # Build paths G_BUILD_DIR: "{{.ROOT_DIR}}/build" G_CORE_COMPONENT_BUILD_DIR: "{{.G_BUILD_DIR}}/core" + G_LOG_VIEWER_WEBUI_BUILD_DIR: "{{.G_BUILD_DIR}}/log-viewer-webui" G_METEOR_BUILD_DIR: "{{.G_BUILD_DIR}}/meteor" + G_NODEJS_14_BUILD_DIR: "{{.G_BUILD_DIR}}/nodejs-14" + G_NODEJS_14_BIN_DIR: "{{.G_NODEJS_14_BUILD_DIR}}/bin" + G_NODEJS_22_BUILD_DIR: "{{.G_BUILD_DIR}}/nodejs-22" + G_NODEJS_22_BIN_DIR: "{{.G_NODEJS_22_BUILD_DIR}}/bin" G_PACKAGE_BUILD_DIR: "{{.G_BUILD_DIR}}/clp-package" G_PACKAGE_VENV_DIR: "{{.G_BUILD_DIR}}/package-venv" G_WEBUI_BUILD_DIR: "{{.G_BUILD_DIR}}/webui" - G_WEBUI_NODEJS_BUILD_DIR: "{{.G_BUILD_DIR}}/webui-nodejs" - G_WEBUI_NODEJS_BIN_DIR: "{{.G_WEBUI_NODEJS_BUILD_DIR}}/bin" + + # Taskfile paths + G_UTILS_TASKFILE: "{{.ROOT_DIR}}/tools/yscope-dev-utils/taskfiles/utils.yml" # Versions G_PACKAGE_VERSION: "0.2.0-dev" @@ -25,6 +39,7 @@ tasks: clean: cmds: - "rm -rf '{{.G_BUILD_DIR}}'" + - task: "clean-log-viewer-webui" - task: "clean-python-component" vars: COMPONENT: "clp-package-utils" @@ -34,6 +49,19 @@ tasks: - task: "clean-python-component" vars: COMPONENT: "job-orchestration" + - task: "clean-webui" + + clean-log-viewer-webui: + cmds: + - "rm -rf 'components/log-viewer-webui/client/node_modules'" + - "rm -rf 'components/log-viewer-webui/node_modules'" + - "rm -rf 'components/log-viewer-webui/server/node_modules'" + - "rm -rf 'components/log-viewer-webui/yscope-log-viewer/node_modules'" + + clean-webui: + cmds: + - "rm -rf 'components/webui/.meteor/local'" + - "rm -rf 'components/webui/node_modules'" clp-json-pkg-tar: cmds: @@ -50,22 +78,46 @@ tasks: STORAGE_ENGINE: "clp" package: + env: + NODE_ENV: "production" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_PACKAGE_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/log-viewer-webui-clients.md5" + - "{{.G_BUILD_DIR}}/package-venv.md5" + - "{{.G_BUILD_DIR}}/webui.md5" + - "{{.G_BUILD_DIR}}/webui-nodejs.md5" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clg" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clo" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package-lock.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/settings.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/src/**/*.js" + - "{{.TASKFILE}}" + - "/etc/os-release" + - "components/clp-package-utils/dist/*.whl" + - "components/clp-py-utils/dist/*.whl" + - "components/job-orchestration/dist/*.whl" + - "components/package-template/src/**/*" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "core" - "clp-package-utils" - "clp-py-utils" - "init" - "job-orchestration" + - "log-viewer-webui-clients" + - "nodejs-14" - "package-venv" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - "webui" - - "webui-nodejs" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - "rsync -a components/package-template/src/ '{{.OUTPUT_DIR}}'" @@ -86,52 +138,49 @@ tasks: "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" - "{{.G_WEBUI_NODEJS_BIN_DIR}}/node" "{{.OUTPUT_DIR}}/bin/" + - >- + rsync -a + "{{.G_NODEJS_14_BIN_DIR}}/node" + "{{.OUTPUT_DIR}}/bin/node-14" + - >- + rsync -a + "{{.G_NODEJS_22_BIN_DIR}}/node" + "{{.OUTPUT_DIR}}/bin/node-22" - "mkdir -p '{{.OUTPUT_DIR}}/var/www/'" - >- rsync -a "{{.G_WEBUI_BUILD_DIR}}/" - "{{.OUTPUT_DIR}}/var/www/" + "{{.OUTPUT_DIR}}/var/www/webui/" + # Avoid using `npm clean-install` because Meteor does not generate a `package-lock.json` file, + # which `clean-install` depends on. + - |- + cd "{{.OUTPUT_DIR}}/var/www/webui/programs/server" + PATH="{{.G_NODEJS_14_BIN_DIR}}":$PATH npm install + - >- + rsync -a + "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}/client" + "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}/yscope-log-viewer" + "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/" + - |- + cd components/log-viewer-webui/server/ + rsync -a \ + package.json package-lock.json settings.json src \ + "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/server/" - |- - cd "{{.OUTPUT_DIR}}/var/www/programs/server" - PATH="{{.G_WEBUI_NODEJS_BIN_DIR}}":$PATH npm install + cd "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/server" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm clean-install # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/package-venv.md5" - - "{{.G_BUILD_DIR}}/webui.md5" - - "{{.G_BUILD_DIR}}/webui-nodejs.md5" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clg" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clo" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" - - "{{.TASKFILE}}" - - "/etc/os-release" - - "components/clp-package-utils/dist/*.whl" - - "components/clp-py-utils/dist/*.whl" - - "components/job-orchestration/dist/*.whl" - - "components/package-template/src/**/*" - generates: ["{{.CHECKSUM_FILE}}"] core: - deps: ["core-submodules", "init"] vars: SRC_DIR: "components/core" - cmds: - - "mkdir -p '{{.G_CORE_COMPONENT_BUILD_DIR}}'" - - "cmake -S '{{.SRC_DIR}}' -B '{{.G_CORE_COMPONENT_BUILD_DIR}}'" - - >- - cmake - --build "{{.G_CORE_COMPONENT_BUILD_DIR}}" - --parallel - --target clg clo clp clp-s reducer-server sources: - - "{{.G_BUILD_DIR}}/core-submodules.md5" + - "{{.G_DEPS_CORE_CHECKSUM_FILE}}" - "{{.SRC_DIR}}/cmake/**/*" - "{{.SRC_DIR}}/CMakeLists.txt" - "{{.SRC_DIR}}/src/**/*" @@ -143,6 +192,15 @@ tasks: - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" + deps: ["deps:core", "init"] + cmds: + - "mkdir -p '{{.G_CORE_COMPONENT_BUILD_DIR}}'" + - "cmake -S '{{.SRC_DIR}}' -B '{{.G_CORE_COMPONENT_BUILD_DIR}}'" + - >- + cmake + --build "{{.G_CORE_COMPONENT_BUILD_DIR}}" + --parallel + --target clg clo clp clp-s reducer-server clp-package-utils: - task: "python-component" @@ -159,20 +217,71 @@ tasks: vars: COMPONENT: "{{.TASK}}" - webui: + log-viewer-webui-clients: + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + OUTPUT_DIR: "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/log-viewer-webui-node-modules.md5" + - "{{.TASKFILE}}" + - "client/package.json" + - "client/src/**/*.css" + - "client/src/**/*.jsx" + - "client/src/webpack.config.js" + - "yscope-log-viewer/package.json" + - "yscope-log-viewer/public/**/*" + - "yscope-log-viewer/src/**/*" + - "yscope-log-viewer/tsconfig.json" + - "yscope-log-viewer/webpack.common.js" + - "yscope-log-viewer/webpack.prod.js" + dir: "components/log-viewer-webui" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - "meteor" - - task: "validate-checksum" + - "log-viewer-webui-node-modules" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - - "webui-node-modules" - dir: "components/webui" - platforms: ["386", "amd64"] + cmds: + - "rm -rf '{{.OUTPUT_DIR}}'" + - for: + - "client" + - "yscope-log-viewer" + cmd: |- + cd "{{.ITEM}}" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm run build -- \ + --output-path "{{.OUTPUT_DIR}}/{{.ITEM}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.OUTPUT_DIR}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + webui: vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_WEBUI_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/meteor.md5" + - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.TASKFILE}}" + - "*" + - ".meteor/*" + - "client/**/*" + - "imports/**/*" + - "server/**/*" + - "tests/**/*" + dir: "components/webui" + platforms: ["386", "amd64"] + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - "init" + - "meteor" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.OUTPUT_DIR}}" + - "webui-node-modules" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - "mkdir -p '{{.OUTPUT_DIR}}'" @@ -187,71 +296,50 @@ tasks: # Remove temp files generated by `meteor build` before checksum - "find node_modules -type f -name '.meteor-portable-2.json' -exec rm {} +" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/meteor.md5" - - "{{.G_BUILD_DIR}}/webui-node-modules.md5" - - "{{.TASKFILE}}" - - "*" - - ".meteor/*" - - "client/**/*" - - "imports/**/*" - - "server/**/*" - - "tests/**/*" - generates: ["{{.CHECKSUM_FILE}}"] - webui-nodejs: + nodejs-22: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" - OUTPUT_DIR: "{{.G_WEBUI_NODEJS_BUILD_DIR}}" + OUTPUT_DIR: "{{.G_NODEJS_22_BUILD_DIR}}" + run: "once" cmds: - task: "nodejs" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" - NODEJS_VERSION: "v14.21.3" + NODEJS_VERSION: "v22.4.0" OUTPUT_DIR: "{{.OUTPUT_DIR}}" - core-submodules: + nodejs-14: internal: true - dir: "components/core" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" - OUTPUT_DIR: "submodules" - deps: - - "init" - - task: "validate-checksum" - vars: - CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" - DATA_DIR: "{{.OUTPUT_DIR}}" + OUTPUT_DIR: "{{.G_NODEJS_14_BUILD_DIR}}" cmds: - - "tools/scripts/deps-download/download-all.sh" - # This command must be last - - task: "compute-checksum" + - task: "nodejs" vars: - DATA_DIR: "{{.OUTPUT_DIR}}" - OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.TASKFILE}}" - - ".gitmodules" - - "tools/scripts/deps-download/**/*" - generates: ["{{.CHECKSUM_FILE}}"] + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + NODEJS_VERSION: "v14.21.3" + OUTPUT_DIR: "{{.OUTPUT_DIR}}" download-and-extract-tar: internal: true - requires: - vars: ["CHECKSUM_FILE", "EXTRACTED_DIR_NAME", "TAR_NAME", "OUTPUT_DIR", "URL_PREFIX"] label: "{{.TASK}}-{{.TAR_NAME}}" vars: OUTPUT_TMP_DIR: "{{.OUTPUT_DIR}}-tmp" EXTRACTED_DIR: "{{.OUTPUT_TMP_DIR}}/{{.EXTRACTED_DIR_NAME}}" TAR_PATH: "{{.OUTPUT_TMP_DIR}}/{{.TAR_NAME}}" + requires: + vars: ["CHECKSUM_FILE", "EXTRACTED_DIR_NAME", "TAR_NAME", "OUTPUT_DIR", "URL_PREFIX"] + sources: ["{{.TASKFILE}}"] + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -260,30 +348,122 @@ tasks: - "mkdir -p '{{.OUTPUT_TMP_DIR}}'" - >- curl --fail --location --show-error - "{{.URL_PREFIX}}/{{.TAR_NAME}}" + "{{trimSuffix "/" .URL_PREFIX}}/{{.TAR_NAME}}" --output "{{.TAR_PATH}}" - "tar xf '{{.TAR_PATH}}' --directory '{{.OUTPUT_TMP_DIR}}'" - "mv '{{.EXTRACTED_DIR}}' '{{.OUTPUT_DIR}}'" - "rm -rf '{{.OUTPUT_TMP_DIR}}'" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: ["{{.TASKFILE}}"] - generates: ["{{.CHECKSUM_FILE}}"] + + # NOTE: The log-viewer-webui has four different node_modules directories: + # * client + # * server + # * log-viewer submodule + # * the top-level one we call "package" + # This means we have to create four different checksums. To allow tasks which depend on this task + # to only have to check one checksum file, we concatenate the four checksum files into one. + log-viewer-webui-node-modules: + internal: true + vars: + # Checksum files + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + CLIENT_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-client-node-modules.md5" + LOG_VIEWER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-log-viewer-node-modules.md5" + PACKAGE_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-package-node-modules.md5" + SERVER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-server-node-modules.md5" + + # Directories + SRC_DIR: "{{.TASKFILE_DIR}}/components/log-viewer-webui" + CLIENT_OUTPUT_DIR: "{{.SRC_DIR}}/client/node_modules" + LOG_VIEWER_OUTPUT_DIR: "{{.SRC_DIR}}/yscope-log-viewer/node_modules" + PACKAGE_OUTPUT_DIR: "{{.SRC_DIR}}/node_modules" + SERVER_OUTPUT_DIR: "{{.SRC_DIR}}/server/node_modules" + sources: + - "{{.G_DEPS_LOG_VIEWER_CHECKSUM_FILE}}" + - "{{.G_BUILD_DIR}}/nodejs-22.md5" + - "{{.TASKFILE}}" + - "client/package.json" + - "client/package-lock.json" + - "package.json" + - "package-lock.json" + - "server/package.json" + - "server/package-lock.json" + - "yscope-log-viewer/package.json" + - "yscope-log-viewer/package-lock.json" + dir: "{{.SRC_DIR}}" + generates: + - "{{.CHECKSUM_FILE}}" + - "{{.CLIENT_CHECKSUM_FILE}}" + - "{{.LOG_VIEWER_CHECKSUM_FILE}}" + - "{{.PACKAGE_CHECKSUM_FILE}}" + - "{{.SERVER_CHECKSUM_FILE}}" + deps: + - "deps:log-viewer" + - "nodejs-22" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CLIENT_CHECKSUM_FILE}}" + DATA_DIR: "{{.CLIENT_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.SERVER_CHECKSUM_FILE}}" + DATA_DIR: "{{.SERVER_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.PACKAGE_CHECKSUM_FILE}}" + DATA_DIR: "{{.PACKAGE_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.LOG_VIEWER_CHECKSUM_FILE}}" + DATA_DIR: "{{.LOG_VIEWER_OUTPUT_DIR}}" + cmds: + - "rm -f {{.CHECKSUM_FILE}}" + - task: "clean-log-viewer-webui" + - "PATH='{{.G_NODEJS_22_BIN_DIR}}':$PATH npm run init" + - |- + cd yscope-log-viewer + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm install + # These commands must be last + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.CLIENT_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.CLIENT_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.LOG_VIEWER_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.LOG_VIEWER_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.PACKAGE_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.PACKAGE_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.SERVER_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.SERVER_CHECKSUM_FILE}}" + # This command must be last + - >- + cat + "{{.CLIENT_CHECKSUM_FILE}}" + "{{.LOG_VIEWER_CHECKSUM_FILE}}" + "{{.PACKAGE_CHECKSUM_FILE}}" + "{{.SERVER_CHECKSUM_FILE}}" + > "{{.CHECKSUM_FILE}}" meteor: + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + METEOR_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x86_64{{ end }}" + METEOR_PLATFORM: "{{ if eq OS \"darwin\" }}osx{{ else }}linux{{ end }}" + METEOR_RELEASE: "2.16" run: "once" preconditions: - sh: >- (test "$(uname -m)" != "aarch64") || (test "$(uname -s)" != "Linux") msg: "Meteor 2.x does not support aarch64 on Linux" - vars: - CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" - METEOR_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x86_64{{ end }}" - METEOR_PLATFORM: "{{ if eq OS \"darwin\" }}osx{{ else }}linux{{ end }}" - METEOR_RELEASE: "2.15" cmds: - task: "download-and-extract-tar" vars: @@ -295,9 +475,6 @@ tasks: nodejs: internal: true - deps: ["init"] - requires: - vars: ["CHECKSUM_FILE", "NODEJS_VERSION", "OUTPUT_DIR"] vars: NODEJS_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x64{{ end }}" NODEJS_VERSION_BASE_URL: "https://nodejs.org/dist/{{.NODEJS_VERSION}}/" @@ -309,6 +486,9 @@ tasks: --max-count 1 "node-v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+-{{OS}}-{{.NODEJS_ARCH}}" | head --lines 1 + requires: + vars: ["CHECKSUM_FILE", "NODEJS_VERSION", "OUTPUT_DIR"] + deps: ["init"] cmds: - task: "download-and-extract-tar" vars: @@ -320,16 +500,19 @@ tasks: package-tar: internal: true - requires: - vars: ["FLAVOUR", "STORAGE_ENGINE"] vars: VERSIONED_PACKAGE_NAME: - sh: | - source /etc/os-release - echo "clp-{{.FLAVOUR}}-${ID}-${VERSION_CODENAME}-$(arch)-v{{.G_PACKAGE_VERSION}}" + sh: "echo clp-{{.FLAVOUR}}-$(arch)-v{{.G_PACKAGE_VERSION}}" OUTPUT_DIR: "{{.G_BUILD_DIR}}/{{.VERSIONED_PACKAGE_NAME}}" OUTPUT_FILE: "{{.OUTPUT_DIR}}.tar.gz" + requires: + vars: ["FLAVOUR", "STORAGE_ENGINE"] + sources: + - "{{.G_BUILD_DIR}}/package.md5" + - "{{.TASKFILE}}" dir: "{{.G_BUILD_DIR}}" + generates: + - "{{.VERSIONED_PACKAGE_NAME}}.tar.gz" deps: ["package"] cmds: - "rm -rf '{{.OUTPUT_DIR}}' '{{.OUTPUT_FILE}}'" @@ -337,12 +520,12 @@ tasks: # `/parents/A` -> `/parents/B` rather than `/parents/A` -> `/parents/B/A` - "rsync --archive '{{.G_PACKAGE_BUILD_DIR}}/' '{{.OUTPUT_DIR}}'" # Set the storage engine for the package - - task: "replace-text" + - task: "utils:replace-text" vars: FILE_PATH: "{{.OUTPUT_DIR}}/lib/python3/site-packages/clp_py_utils/clp_config.py" SED_EXP: >- s/([[:space:]]*storage_engine: str = ")[^"]+"/\1{{.STORAGE_ENGINE}}"/ - - task: "replace-text" + - task: "utils:replace-text" vars: FILE_PATH: "{{.OUTPUT_DIR}}/etc/clp-config.yml" SED_EXP: >- @@ -351,62 +534,44 @@ tasks: tar czf '{{.OUTPUT_FILE}}' --directory '{{.G_BUILD_DIR}}' --dereference '{{.VERSIONED_PACKAGE_NAME}}' - sources: - - "{{.G_BUILD_DIR}}/package.md5" - - "{{.TASKFILE}}" - generates: - - "{{.VERSIONED_PACKAGE_NAME}}.tar.gz" package-venv: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_PACKAGE_VENV_DIR}}" + sources: + - "{{.ROOT_DIR}}/requirements.txt" + - "{{.TASKFILE}}" + - "/etc/os-release" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: "create-venv" + - task: "utils:create-venv" vars: LABEL: "package" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "requirements.txt" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/requirements.txt" - - "{{.TASKFILE}}" - - "/etc/os-release" - generates: ["{{.CHECKSUM_FILE}}"] python-component: internal: true - requires: - vars: ["COMPONENT"] label: "{{.COMPONENT}}" - deps: - - task: "component-venv" - vars: - COMPONENT: "{{.COMPONENT}}" - OUTPUT_DIR: "{{.VENV_DIR}}" vars: PACKAGE: sh: "echo {{.COMPONENT}} | tr - _" VENV_DIR: "{{.G_BUILD_DIR}}/{{.COMPONENT}}/venv" - dir: "components/{{.COMPONENT}}" - cmds: - - task: "clean-python-component" - vars: - COMPONENT: "{{.COMPONENT}}" - - |- - . "{{.VENV_DIR}}/bin/activate" - poetry build --format wheel + requires: + vars: ["COMPONENT"] sources: - "{{.G_BUILD_DIR}}/{{.COMPONENT}}_venv.md5" - "{{.PACKAGE}}/**/*" @@ -414,8 +579,21 @@ tasks: - "{{.TASKFILE}}" - "/etc/os-release" - "pyproject.toml" + dir: "components/{{.COMPONENT}}" generates: - "dist/*.whl" + deps: + - task: "component-venv" + vars: + COMPONENT: "{{.COMPONENT}}" + OUTPUT_DIR: "{{.VENV_DIR}}" + cmds: + - task: "clean-python-component" + vars: + COMPONENT: "{{.COMPONENT}}" + - |- + . "{{.VENV_DIR}}/bin/activate" + poetry build --format wheel webui-node-modules: internal: true @@ -423,11 +601,17 @@ tasks: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" WEBUI_SRC_DIR: "{{.ROOT_DIR}}/components/webui" OUTPUT_DIR: "{{.WEBUI_SRC_DIR}}/node_modules" + sources: + - "{{.G_BUILD_DIR}}/meteor.md5" + - "{{.TASKFILE}}" + - ".meteor/packages" + - "package.json" dir: "{{.WEBUI_SRC_DIR}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - "meteor" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -435,139 +619,53 @@ tasks: - "rm -rf '{{.OUTPUT_DIR}}'" - "PATH='{{.G_METEOR_BUILD_DIR}}':$PATH meteor npm install --production" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/meteor.md5" - - "{{.TASKFILE}}" - - ".meteor/packages" - - "package.json" - generates: ["{{.CHECKSUM_FILE}}"] component-venv: internal: true - requires: - vars: ["COMPONENT", "OUTPUT_DIR"] label: "{{.COMPONENT}}-venv" - dir: "components/{{.COMPONENT}}" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.COMPONENT}}-venv.md5" + requires: + vars: ["COMPONENT", "OUTPUT_DIR"] + sources: + - "{{.ROOT_DIR}}/requirements.txt" + - "{{.TASKFILE}}" + - "/etc/os-release" + - "pyproject.toml" + dir: "components/{{.COMPONENT}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: "create-venv" + - task: "utils:create-venv" vars: LABEL: "{{.COMPONENT}}" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "{{.ROOT_DIR}}/requirements.txt" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/requirements.txt" - - "{{.TASKFILE}}" - - "/etc/os-release" - - "pyproject.toml" - generates: ["{{.CHECKSUM_FILE}}"] - clean-python-component: internal: true + label: "clean-{{.COMPONENT}}" requires: vars: ["COMPONENT"] - label: "clean-{{.COMPONENT}}" dir: "components/{{.COMPONENT}}" cmds: - "rm -rf dist" - create-venv: - internal: true - requires: - vars: ["LABEL", "OUTPUT_DIR", "REQUIREMENTS_FILE"] - label: "create-venv-{{.LABEL}}" - cmds: - - "rm -rf '{{.OUTPUT_DIR}}'" - - "python3 -m venv '{{.OUTPUT_DIR}}'" - # Remove calls to `hash` from the venv activation script since Task uses `gosh` rather than - # `bash`. - # NOTE: Older versions of Python's venv would only call `hash` if they detected the running - # shell was one that had the command, but that's not the case in newer versions. - - task: "replace-text" - vars: - FILE_PATH: "{{.OUTPUT_DIR}}/bin/activate" - SED_EXP: >- - s/^([[:space:]]*)hash[[:space:]]+.*/\1true/g - - |- - . "{{.OUTPUT_DIR}}/bin/activate" - pip3 install --upgrade pip - pip3 install --upgrade -r "{{.REQUIREMENTS_FILE}}" - init: internal: true - run: "once" silent: true + run: "once" cmd: "mkdir -p '{{.G_BUILD_DIR}}'" - - compute-checksum: - desc: "Tries to compute a checksum for the given directory and output it to a file." - internal: true - # Ignore errors so that dependent tasks don't fail - ignore_error: true - silent: true - requires: - vars: ["DATA_DIR", "OUTPUT_FILE"] - cmds: - - >- - tar cf - - --directory "{{.DATA_DIR}}" - --group=0 - --mtime='UTC 1970-01-01' - --numeric-owner - --owner=0 - --sort=name - {{.CHECKSUM_TAR_BASE_ARGS}} . 2> /dev/null - | md5sum > {{.OUTPUT_FILE}} - - validate-checksum: - desc: "Validates the checksum of the given directory matches the checksum in the given file, or - deletes the checksum file otherwise." - internal: true - silent: true - requires: - vars: ["CHECKSUM_FILE", "DATA_DIR"] - vars: - TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" - cmds: - - task: "compute-checksum" - vars: - DATA_DIR: "{{.DATA_DIR}}" - OUTPUT_FILE: "{{.TMP_CHECKSUM_FILE}}" - - defer: "rm -f '{{.TMP_CHECKSUM_FILE}}'" - # Check that the directory exists and the checksum matches; otherwise delete the checksum file - - >- - ( - test -d "{{.DATA_DIR}}" - && diff -q '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}' 2> /dev/null - ) || rm -f '{{.CHECKSUM_FILE}}' - - replace-text: - desc: "Task to replace some text in a file using sed." - internal: true - requires: - vars: ["FILE_PATH", "SED_EXP"] - cmds: - - |- - # NOTE: - # 1. We can't use `sed -i` since `-i` has different syntax on Linux and macOS - # 2. We can't use `--regexp` instead of `-E` since `--regexp` is not supported on macOS - src="{{.FILE_PATH}}" - dst="{{.FILE_PATH}}.tmp" - sed -E '{{.SED_EXP}}' "${src}" > "${dst}" - mv "${dst}" "${src}" diff --git a/components/clp-package-utils/clp_package_utils/__init__.py b/components/clp-package-utils/clp_package_utils/__init__.py index e69de29bb..5253a87e5 100644 --- a/components/clp-package-utils/clp_package_utils/__init__.py +++ b/components/clp-package-utils/clp_package_utils/__init__.py @@ -0,0 +1,13 @@ +import logging + +# Set up console logging +logging_console_handler = logging.StreamHandler() +logging_formatter = logging.Formatter( + "%(asctime)s.%(msecs)03d %(levelname)s [%(module)s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%S" +) +logging_console_handler.setFormatter(logging_formatter) + +# Set up root logger +root_logger = logging.getLogger() +root_logger.setLevel(logging.INFO) +root_logger.addHandler(logging_console_handler) diff --git a/components/clp-package-utils/clp_package_utils/general.py b/components/clp-package-utils/clp_package_utils/general.py index 29c421109..5fae8166f 100644 --- a/components/clp-package-utils/clp_package_utils/general.py +++ b/components/clp-package-utils/clp_package_utils/general.py @@ -6,12 +6,16 @@ import socket import subprocess import typing +import uuid +from enum import auto +from typing import List, Optional, Tuple import yaml from clp_py_utils.clp_config import ( CLP_DEFAULT_CREDENTIALS_FILE_PATH, CLPConfig, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, @@ -24,8 +28,13 @@ read_yaml_config_file, validate_path_could_be_dir, ) +from strenum import KebabCaseStrEnum # CONSTANTS +EXTRACT_FILE_CMD = "x" +EXTRACT_IR_CMD = "i" +EXTRACT_JSON_CMD = "j" + # Paths CONTAINER_CLP_HOME = pathlib.Path("/") / "opt" / "clp" CONTAINER_INPUT_LOGS_ROOT_DIR = pathlib.Path("/") / "mnt" / "logs" @@ -38,6 +47,13 @@ class DockerMountType(enum.IntEnum): BIND = 0 +class JobType(KebabCaseStrEnum): + COMPRESSION = auto() + FILE_EXTRACTION = auto() + IR_EXTRACTION = auto() + SEARCH = auto() + + class DockerMount: def __init__( self, @@ -69,6 +85,7 @@ def __init__(self, clp_home: pathlib.Path, docker_clp_home: pathlib.Path): self.data_dir: typing.Optional[DockerMount] = None self.logs_dir: typing.Optional[DockerMount] = None self.archives_output_dir: typing.Optional[DockerMount] = None + self.stream_output_dir: typing.Optional[DockerMount] = None def get_clp_home(): @@ -90,6 +107,14 @@ def get_clp_home(): return clp_home.resolve() +def generate_container_name(job_type: str) -> str: + """ + :param job_type: + :return: A unique container name for the given job type. + """ + return f"clp-{job_type}-{str(uuid.uuid4())[-4:]}" + + def check_dependencies(): try: subprocess.run( @@ -176,12 +201,15 @@ def is_path_already_mounted( return host_path_relative_to_mounted_root == container_path_relative_to_mounted_root -def generate_container_config(clp_config: CLPConfig, clp_home: pathlib.Path): +def generate_container_config( + clp_config: CLPConfig, clp_home: pathlib.Path +) -> Tuple[CLPConfig, CLPDockerMounts]: """ Copies the given config and sets up mounts mapping the relevant host paths into the container :param clp_config: :param clp_home: + :return: The container config and the mounts. """ container_clp_config = clp_config.copy(deep=True) @@ -224,9 +252,73 @@ def generate_container_config(clp_config: CLPConfig, clp_home: pathlib.Path): container_clp_config.archive_output.directory, ) + container_clp_config.stream_output.directory = pathlib.Path("/") / "mnt" / "stream-output" + if not is_path_already_mounted( + clp_home, + CONTAINER_CLP_HOME, + clp_config.stream_output.directory, + container_clp_config.stream_output.directory, + ): + docker_mounts.stream_output_dir = DockerMount( + DockerMountType.BIND, + clp_config.stream_output.directory, + container_clp_config.stream_output.directory, + ) + return container_clp_config, docker_mounts +def dump_container_config( + container_clp_config: CLPConfig, clp_config: CLPConfig, container_name: str +) -> Tuple[pathlib.Path, pathlib.Path]: + """ + Writes the given config to the logs directory so that it's accessible in the container. + :param container_clp_config: The config to write. + :param clp_config: The corresponding config on the host (used to determine the logs directory). + :param container_name: + :return: The path to the config file in the container and on the host. + """ + container_config_filename = f".{container_name}-config.yml" + config_file_path_on_host = clp_config.logs_directory / container_config_filename + config_file_path_on_container = container_clp_config.logs_directory / container_config_filename + with open(config_file_path_on_host, "w") as f: + yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + + return config_file_path_on_container, config_file_path_on_host + + +def generate_container_start_cmd( + container_name: str, container_mounts: List[Optional[DockerMount]], container_image: str +) -> List[str]: + """ + Generates the command to start a container with the given mounts and name. + :param container_name: + :param container_mounts: + :param container_image: + :return: The command. + """ + clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" + # fmt: off + container_start_cmd = [ + "docker", "run", + "-i", + "--rm", + "--network", "host", + "-w", str(CONTAINER_CLP_HOME), + "-e", f"PYTHONPATH={clp_site_packages_dir}", + "-u", f"{os.getuid()}:{os.getgid()}", + "--name", container_name, + "--log-driver", "local" + ] + for mount in container_mounts: + if mount: + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) + container_start_cmd.append(container_image) + + return container_start_cmd + + def validate_config_key_existence(config, key): try: value = get_config_value(config, key) @@ -235,7 +327,7 @@ def validate_config_key_existence(config, key): return value -def validate_and_load_config_file( +def load_config_file( config_file_path: pathlib.Path, default_config_file_path: pathlib.Path, clp_home: pathlib.Path ): if config_file_path.exists(): @@ -391,6 +483,7 @@ def validate_results_cache_config( def validate_worker_config(clp_config: CLPConfig): clp_config.validate_input_logs_dir() clp_config.validate_archive_output_dir() + clp_config.validate_stream_output_dir() def validate_webui_config( @@ -407,3 +500,16 @@ def validate_webui_config( raise ValueError(f"{WEBUI_COMPONENT_NAME} logs directory is invalid: {ex}") validate_port(f"{WEBUI_COMPONENT_NAME}.port", clp_config.webui.host, clp_config.webui.port) + + +def validate_log_viewer_webui_config(clp_config: CLPConfig, settings_json_path: pathlib.Path): + if not settings_json_path.exists(): + raise ValueError( + f"{WEBUI_COMPONENT_NAME} {settings_json_path} is not a valid path to settings.json" + ) + + validate_port( + f"{LOG_VIEWER_WEBUI_COMPONENT_NAME}.port", + clp_config.log_viewer_webui.host, + clp_config.log_viewer_webui.port, + ) diff --git a/components/clp-package-utils/clp_package_utils/scripts/compress.py b/components/clp-package-utils/clp_package_utils/scripts/compress.py index 61495a4cd..efd3180ae 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/compress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/compress.py @@ -1,32 +1,24 @@ import argparse import logging -import os import pathlib import subprocess import sys import uuid -import yaml - from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, CONTAINER_INPUT_LOGS_ROOT_DIR, + dump_container_config, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, ) -# Setup logging -# Create logger logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) def main(argv): @@ -57,51 +49,32 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() + # Validate and load necessary credentials validate_and_load_db_credentials_file(clp_config, clp_home, False) except: logger.exception("Failed to load config.") return -1 - container_name = f"clp-compressor-{str(uuid.uuid4())[-4:]}" + container_name = generate_container_name(str(JobType.COMPRESSION)) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on - necessary_mounts = [mounts.input_logs_dir, mounts.data_dir, mounts.logs_dir] - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - container_start_cmd.append(clp_config.execution_container) + necessary_mounts = [mounts.clp_home, mounts.input_logs_dir, mounts.data_dir, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off compress_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.compress", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), "--remove-path-prefix", str(CONTAINER_INPUT_LOGS_ROOT_DIR), ] # fmt: on @@ -140,7 +113,7 @@ def main(argv): subprocess.run(cmd, check=True) # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 diff --git a/components/clp-package-utils/clp_package_utils/scripts/decompress.py b/components/clp-package-utils/clp_package_utils/scripts/decompress.py index f20291b50..325f2add6 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/decompress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/decompress.py @@ -1,71 +1,70 @@ import argparse import logging -import os import pathlib import subprocess import sys -import uuid +from typing import Optional -import yaml +from clp_py_utils.clp_config import CLPConfig from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, DockerMount, DockerMountType, + dump_container_config, + EXTRACT_FILE_CMD, + EXTRACT_IR_CMD, + EXTRACT_JSON_CMD, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, validate_path_could_be_dir, ) -# Setup logging -# Create logger -logger = logging.getLogger("clp") -logger.setLevel(logging.DEBUG) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) +logger = logging.getLogger(__file__) -def main(argv): - clp_home = get_clp_home() - default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH - - args_parser = argparse.ArgumentParser(description="Decompresses logs") - args_parser.add_argument( - "--config", - "-c", - type=str, - default=str(default_config_file_path), - help="CLP package configuration file.", - ) - args_parser.add_argument("paths", metavar="PATH", nargs="*", help="Files to decompress.") - args_parser.add_argument("-f", "--files-from", help="A file listing all files to decompress.") - args_parser.add_argument( - "-d", "--extraction-dir", metavar="DIR", default=".", help="Decompress files into DIR" - ) - parsed_args = args_parser.parse_args(argv[1:]) - - # Validate and load config file +def validate_and_load_config( + clp_home: pathlib.Path, + config_file_path: pathlib.Path, + default_config_file_path: pathlib.Path, +) -> Optional[CLPConfig]: + """ + Validates and loads the config file. + :param clp_home: + :param config_file_path: + :param default_config_file_path: + :return: The config object on success, None otherwise. + """ try: - config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() + # Validate and load necessary credentials validate_and_load_db_credentials_file(clp_config, clp_home, False) + return clp_config except: logger.exception("Failed to load config.") - return -1 + return None + - paths_to_decompress_file_path = None +def handle_extract_file_cmd( + parsed_args, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the file extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + paths_to_extract_file_path = None if parsed_args.files_from: - paths_to_decompress_file_path = pathlib.Path(parsed_args.files_from) + paths_to_extract_file_path = pathlib.Path(parsed_args.files_from) # Validate extraction directory extraction_dir = pathlib.Path(parsed_args.extraction_dir).resolve() @@ -74,81 +73,199 @@ def main(argv): except ValueError as ex: logger.error(f"extraction-dir is invalid: {ex}") return -1 - extraction_dir.mkdir(exist_ok=True) - container_name = f"clp-decompressor-{str(uuid.uuid4())[-4:]}" + # Validate and load config file + clp_config = validate_and_load_config( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + container_name = generate_container_name(str(JobType.FILE_EXTRACTION)) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) - - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) # Set up mounts + extraction_dir.mkdir(exist_ok=True) container_extraction_dir = pathlib.Path("/") / "mnt" / "extraction-dir" necessary_mounts = [ + mounts.clp_home, mounts.data_dir, mounts.logs_dir, mounts.archives_output_dir, DockerMount(DockerMountType.BIND, extraction_dir, container_extraction_dir), ] - container_paths_to_decompress_file_path = None - if paths_to_decompress_file_path: - container_paths_to_decompress_file_path = ( - pathlib.Path("/") / "mnt" / "paths-to-decompress.txt" - ) + container_paths_to_extract_file_path = None + if paths_to_extract_file_path: + container_paths_to_extract_file_path = pathlib.Path("/") / "mnt" / "paths-to-extract.txt" necessary_mounts.append( DockerMount( DockerMountType.BIND, - paths_to_decompress_file_path, - container_paths_to_decompress_file_path, + paths_to_extract_file_path, + container_paths_to_extract_file_path, ) ) - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - - container_start_cmd.append(clp_config.execution_container) + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off - decompress_cmd = [ + extract_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.decompress", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), + EXTRACT_FILE_CMD, "-d", str(container_extraction_dir), ] # fmt: on for path in parsed_args.paths: - decompress_cmd.append(path) - if container_paths_to_decompress_file_path: - decompress_cmd.append("--input-list") - decompress_cmd.append(container_paths_to_decompress_file_path) + extract_cmd.append(path) + if container_paths_to_extract_file_path: + extract_cmd.append("--input-list") + extract_cmd.append(container_paths_to_extract_file_path) - cmd = container_start_cmd + decompress_cmd - subprocess.run(cmd, check=True) + cmd = container_start_cmd + extract_cmd + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError: + logger.exception("Docker or file extraction command failed.") + return -1 # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 +def handle_extract_stream_cmd( + parsed_args, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the stream extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate and load config file + clp_config = validate_and_load_config( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + container_name = generate_container_name(str(JobType.IR_EXTRACTION)) + container_clp_config, mounts = generate_container_config(clp_config, clp_home) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) + necessary_mounts = [mounts.clp_home, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) + + # fmt: off + job_command = parsed_args.command + extract_cmd = [ + "python3", + "-m", "clp_package_utils.scripts.native.decompress", + "--config", str(generated_config_path_on_container), + job_command + ] + # fmt: on + + if EXTRACT_IR_CMD == job_command: + extract_cmd.append(str(parsed_args.msg_ix)) + if parsed_args.orig_file_id: + extract_cmd.append("--orig-file-id") + extract_cmd.append(str(parsed_args.orig_file_id)) + else: + extract_cmd.append("--orig-file-path") + extract_cmd.append(str(parsed_args.orig_file_path)) + if parsed_args.target_uncompressed_size: + extract_cmd.append("--target-uncompressed-size") + extract_cmd.append(str(parsed_args.target_uncompressed_size)) + elif EXTRACT_JSON_CMD == job_command: + extract_cmd.append(str(parsed_args.archive_id)) + if parsed_args.target_chunk_size: + extract_cmd.append("--target-chunk-size") + extract_cmd.append(str(parsed_args.target_chunk_size)) + else: + logger.error(f"Unexpected command: {job_command}") + return -1 + + cmd = container_start_cmd + extract_cmd + + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError: + logger.exception("Docker or stream extraction command failed.") + return -1 + + # Remove generated files + generated_config_path_on_host.unlink() + + return 0 + + +def main(argv): + clp_home = get_clp_home() + default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH + + args_parser = argparse.ArgumentParser(description="Decompresses logs") + args_parser.add_argument( + "--config", + "-c", + default=str(default_config_file_path), + help="CLP configuration file.", + ) + command_args_parser = args_parser.add_subparsers(dest="command", required=True) + + # File extraction command parser + file_extraction_parser = command_args_parser.add_parser(EXTRACT_FILE_CMD) + file_extraction_parser.add_argument( + "paths", metavar="PATH", nargs="*", help="Files to extract." + ) + file_extraction_parser.add_argument( + "-f", "--files-from", help="A file listing all files to extract." + ) + file_extraction_parser.add_argument( + "-d", "--extraction-dir", metavar="DIR", default=".", help="Extract files into DIR." + ) + + # IR extraction command parser + ir_extraction_parser = command_args_parser.add_parser(EXTRACT_IR_CMD) + ir_extraction_parser.add_argument("msg_ix", type=int, help="Message index.") + ir_extraction_parser.add_argument( + "--target-uncompressed-size", type=int, help="Target uncompressed IR size." + ) + + group = ir_extraction_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--orig-file-id", type=str, help="Original file's ID.") + group.add_argument("--orig-file-path", type=str, help="Original file's path.") + + # JSON extraction command parser + json_extraction_parser = command_args_parser.add_parser(EXTRACT_JSON_CMD) + json_extraction_parser.add_argument("archive_id", type=str, help="Archive ID") + json_extraction_parser.add_argument( + "--target-chunk-size", + type=int, + help="Target chunk size (B).", + ) + + parsed_args = args_parser.parse_args(argv[1:]) + + command = parsed_args.command + if EXTRACT_FILE_CMD == command: + return handle_extract_file_cmd(parsed_args, clp_home, default_config_file_path) + elif command in (EXTRACT_IR_CMD, EXTRACT_JSON_CMD): + return handle_extract_stream_cmd(parsed_args, clp_home, default_config_file_path) + else: + logger.exception(f"Unexpected command: {command}") + return -1 + + if "__main__" == __name__: sys.exit(main(sys.argv)) diff --git a/components/clp-package-utils/clp_package_utils/scripts/del_archives.py b/components/clp-package-utils/clp_package_utils/scripts/del_archives.py new file mode 100644 index 000000000..54d959771 --- /dev/null +++ b/components/clp-package-utils/clp_package_utils/scripts/del_archives.py @@ -0,0 +1,103 @@ +import argparse +import logging +import subprocess +import sys +from pathlib import Path + +from clp_package_utils.general import ( + CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, + dump_container_config, + generate_container_config, + generate_container_name, + generate_container_start_cmd, + get_clp_home, + load_config_file, + validate_and_load_db_credentials_file, +) + +logger = logging.getLogger(__file__) + + +def main(argv): + clp_home = get_clp_home() + default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH + + args_parser = argparse.ArgumentParser( + description="Deletes archives that fall within the specified time range." + ) + args_parser.add_argument( + "--config", + "-c", + default=str(default_config_file_path), + help="CLP package configuration file.", + ) + args_parser.add_argument( + "--begin-ts", + type=int, + default=0, + help="Time-range lower-bound (inclusive) as milliseconds from the UNIX epoch.", + ) + args_parser.add_argument( + "--end-ts", + type=int, + required=True, + help="Time-range upper-bound (include) as milliseconds from the UNIX epoch.", + ) + parsed_args = args_parser.parse_args(argv[1:]) + + # Validate and load config file + try: + config_file_path = Path(parsed_args.config) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) + clp_config.validate_logs_dir() + + # Validate and load necessary credentials + validate_and_load_db_credentials_file(clp_config, clp_home, False) + except: + logger.exception("Failed to load config.") + return -1 + + # Validate the input timestamp + begin_ts = parsed_args.begin_ts + end_ts = parsed_args.end_ts + if begin_ts > end_ts: + logger.error("begin-ts must be <= end-ts") + return -1 + if end_ts < 0 or begin_ts < 0: + logger.error("begin_ts and end_ts must be non-negative.") + return -1 + + container_name = generate_container_name("del-archives") + + container_clp_config, mounts = generate_container_config(clp_config, clp_home) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) + + necessary_mounts = [mounts.clp_home, mounts.logs_dir, mounts.archives_output_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) + + # fmt: off + del_archive_cmd = [ + "python3", + "-m", "clp_package_utils.scripts.native.del_archives", + "--config", str(generated_config_path_on_container), + str(begin_ts), + str(end_ts) + + ] + # fmt: on + + cmd = container_start_cmd + del_archive_cmd + subprocess.run(cmd, check=True) + + # Remove generated files + generated_config_path_on_host.unlink() + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/compress.py b/components/clp-package-utils/clp_package_utils/scripts/native/compress.py index a08602007..b6d9bb7eb 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/compress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/compress.py @@ -20,18 +20,10 @@ from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, get_clp_home, - validate_and_load_config_file, + load_config_file, ) -# Setup logging -# Create logger logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) def print_compression_job_status(job_row, current_time): @@ -170,9 +162,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_input_logs_dir() clp_config.validate_logs_dir() except: diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py b/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py index 9ca3ab7b6..d16cdcb6f 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py @@ -1,74 +1,250 @@ import argparse +import asyncio import logging import pathlib import subprocess import sys import uuid +from contextlib import closing +from typing import Optional import yaml -from clp_py_utils.clp_config import CLPConfig +from clp_py_utils.clp_config import CLP_METADATA_TABLE_PREFIX, CLPConfig, Database +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.job_config import ( + ExtractIrJobConfig, + ExtractJsonJobConfig, + QueryJobConfig, +) from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, + EXTRACT_FILE_CMD, + EXTRACT_IR_CMD, + EXTRACT_JSON_CMD, get_clp_home, - validate_and_load_config_file, + load_config_file, +) +from clp_package_utils.scripts.native.utils import ( + run_function_in_process, + submit_query_job, + wait_for_query_job, ) -# Setup logging -# Create logger logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) -def decompress_paths( +def get_orig_file_id(db_config: Database, path: str) -> Optional[str]: + """ + :param db_config: + :param path: Path of the original file. + :return: The ID of an original file which has the given path, or None if no such file exists. + NOTE: Multiple original files may have the same path in which case this method returns the ID of + only one of them. + """ + sql_adapter = SQL_Adapter(db_config) + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + db_cursor.execute( + f"SELECT orig_file_id FROM `{CLP_METADATA_TABLE_PREFIX}files` WHERE path = (%s)", + (path,), + ) + results = db_cursor.fetchall() + db_conn.commit() + + if len(results) == 0: + logger.error("No file found for the given path.") + return None + + if len(results) > 1: + logger.warning( + "Multiple files found for the given path." + " Returning the orig_file_id of one of them." + ) + + return results[0]["orig_file_id"] + + +def submit_and_monitor_extraction_job_in_db( + db_config: Database, + job_type: QueryJobType, + job_config: QueryJobConfig, +) -> int: + """ + Submits a stream extraction job to the scheduler and waits until it finishes. + :param db_config: + :param job_type: + :param job_config: + :return: 0 on success, -1 otherwise. + """ + sql_adapter = SQL_Adapter(db_config) + job_id = submit_query_job(sql_adapter, job_config, job_type) + job_status = wait_for_query_job(sql_adapter, job_id) + + if QueryJobStatus.SUCCEEDED == job_status: + logger.info(f"Finished extraction job {job_id}.") + return 0 + + logger.error(f"Extraction job {job_id} finished with unexpected status: {job_status.to_str()}.") + return -1 + + +def handle_extract_stream_cmd( + parsed_args: argparse.Namespace, + clp_home: pathlib.Path, + default_config_file_path: pathlib.Path, +) -> int: + """ + Handles the stream extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate and load config file + clp_config = validate_and_load_config_file( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + command = parsed_args.command + + job_config: QueryJobConfig + job_type: QueryJobType + if EXTRACT_IR_CMD == command: + job_type = QueryJobType.EXTRACT_IR + orig_file_id: str + if parsed_args.orig_file_id: + orig_file_id = parsed_args.orig_file_id + else: + orig_file_path = parsed_args.orig_file_path + orig_file_id = get_orig_file_id(clp_config.database, orig_file_path) + if orig_file_id is None: + logger.error(f"Cannot find orig_file_id corresponding to '{orig_file_path}'.") + return -1 + job_config = ExtractIrJobConfig( + orig_file_id=orig_file_id, + msg_ix=parsed_args.msg_ix, + target_uncompressed_size=parsed_args.target_uncompressed_size, + ) + elif EXTRACT_JSON_CMD == command: + job_type = QueryJobType.EXTRACT_JSON + job_config = ExtractJsonJobConfig( + archive_id=parsed_args.archive_id, target_chunk_size=parsed_args.target_chunk_size + ) + else: + logger.error(f"Unsupported stream extraction command: {command}") + return -1 + + try: + return asyncio.run( + run_function_in_process( + submit_and_monitor_extraction_job_in_db, + clp_config.database, + job_type, + job_config, + ) + ) + except asyncio.CancelledError: + logger.error("Stream extraction cancelled.") + return -1 + + +def validate_and_load_config_file( clp_home: pathlib.Path, - paths, - list_path: pathlib.Path, - clp_config: CLPConfig, - archives_dir: pathlib.Path, - logs_dir: pathlib.Path, - extraction_dir: pathlib.Path, -): + config_file_path: pathlib.Path, + default_config_file_path: pathlib.Path, +) -> Optional[CLPConfig]: + """ + Validates and loads the config file. + :param clp_home: + :param config_file_path: + :param default_config_file_path: + :return: clp_config on success, None otherwise. + """ + try: + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) + clp_config.validate_archive_output_dir() + clp_config.validate_logs_dir() + return clp_config + except Exception: + logger.exception("Failed to load config.") + return None + + +def handle_extract_file_cmd( + parsed_args: argparse.Namespace, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the file extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate paths were specified using only one method + if len(parsed_args.paths) > 0 and parsed_args.files_from is not None: + logger.error("Paths cannot be specified both on the command line and through a file.") + return -1 + + # Validate extraction directory + extraction_dir = pathlib.Path(parsed_args.extraction_dir) + if not extraction_dir.is_dir(): + logger.error(f"extraction-dir ({extraction_dir}) is not a valid directory.") + return -1 + + # Validate and load config file + clp_config = validate_and_load_config_file( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + paths = parsed_args.paths + list_path = parsed_args.files_from + + logs_dir = clp_config.logs_directory + archives_dir = clp_config.archive_output.directory + # Generate database config file for clp db_config_file_path = logs_dir / f".decompress-db-config-{uuid.uuid4()}.yml" with open(db_config_file_path, "w") as f: yaml.safe_dump(clp_config.database.get_clp_connection_params_and_type(True), f) # fmt: off - decompression_cmd = [ + extract_cmd = [ str(clp_home / "bin" / "clp"), "x", str(archives_dir), str(extraction_dir), "--db-config-file", str(db_config_file_path), ] # fmt: on - files_to_decompress_list_path = None + + files_to_extract_list_path = None if list_path is not None: - decompression_cmd.append("-f") - decompression_cmd.append(str(list_path)) + extract_cmd.append("-f") + extract_cmd.append(str(list_path)) elif len(paths) > 0: # Write paths to file - files_to_decompress_list_path = logs_dir / f"paths-to-decompress-{uuid.uuid4()}.txt" - with open(files_to_decompress_list_path, "w") as stream: + files_to_extract_list_path = logs_dir / f"paths-to-extract-{uuid.uuid4()}.txt" + with open(files_to_extract_list_path, "w") as stream: for path in paths: stream.write(path + "\n") - decompression_cmd.append("-f") - decompression_cmd.append(str(files_to_decompress_list_path)) + extract_cmd.append("-f") + extract_cmd.append(str(files_to_extract_list_path)) - proc = subprocess.Popen(decompression_cmd) + proc = subprocess.Popen(extract_cmd) return_code = proc.wait() if 0 != return_code: - logger.error(f"Decompression failed, return_code={return_code}") + logger.error(f"File extraction failed, return_code={return_code}") return return_code # Remove generated files - if files_to_decompress_list_path is not None: - files_to_decompress_list_path.unlink() + if files_to_extract_list_path is not None: + files_to_extract_list_path.unlink() db_config_file_path.unlink() return 0 @@ -86,45 +262,49 @@ def main(argv): default=str(default_config_file_path), help="CLP configuration file.", ) - args_parser.add_argument("paths", metavar="PATH", nargs="*", help="Paths to decompress.") - args_parser.add_argument("-f", "--files-from", help="Decompress all paths in the given list.") - args_parser.add_argument( - "-d", "--extraction-dir", metavar="DIR", help="Decompress files into DIR", default="." - ) - parsed_args = args_parser.parse_args(argv[1:]) + command_args_parser = args_parser.add_subparsers(dest="command", required=True) - # Validate paths were specified using only one method - if len(parsed_args.paths) > 0 and parsed_args.files_from is not None: - args_parser.error("Paths cannot be specified both on the command line and through a file.") + # File extraction command parser + file_extraction_parser = command_args_parser.add_parser(EXTRACT_FILE_CMD) + file_extraction_parser.add_argument( + "paths", metavar="PATH", nargs="*", help="Files to extract." + ) + file_extraction_parser.add_argument( + "-f", "--files-from", help="A file listing all files to extract." + ) + file_extraction_parser.add_argument( + "-d", "--extraction-dir", metavar="DIR", default=".", help="Extract files into DIR." + ) - # Validate extraction directory - extraction_dir = pathlib.Path(parsed_args.extraction_dir) - if not extraction_dir.is_dir(): - logger.error(f"extraction-dir ({extraction_dir}) is not a valid directory.") - return -1 + # IR extraction command parser + ir_extraction_parser = command_args_parser.add_parser(EXTRACT_IR_CMD) + ir_extraction_parser.add_argument("msg_ix", type=int, help="Message index.") + ir_extraction_parser.add_argument( + "--target-uncompressed-size", type=int, help="Target uncompressed IR size." + ) - # Validate and load config file - try: - config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) - clp_config.validate_archive_output_dir() - clp_config.validate_logs_dir() - except: - logger.exception("Failed to load config.") - return -1 + group = ir_extraction_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--orig-file-id", type=str, help="Original file's ID.") + group.add_argument("--orig-file-path", type=str, help="Original file's path.") - return decompress_paths( - clp_home, - parsed_args.paths, - parsed_args.files_from, - clp_config, - clp_config.archive_output.directory, - clp_config.logs_directory, - extraction_dir, + # JSON extraction command parser + json_extraction_parser = command_args_parser.add_parser(EXTRACT_JSON_CMD) + json_extraction_parser.add_argument("archive_id", type=str, help="Archive ID") + json_extraction_parser.add_argument( + "--target-chunk-size", type=int, help="Target chunk size (B)." ) + parsed_args = args_parser.parse_args(argv[1:]) + + command = parsed_args.command + if EXTRACT_FILE_CMD == command: + return handle_extract_file_cmd(parsed_args, clp_home, default_config_file_path) + elif command in (EXTRACT_IR_CMD, EXTRACT_JSON_CMD): + return handle_extract_stream_cmd(parsed_args, clp_home, default_config_file_path) + else: + logger.exception(f"Unexpected command: {command}") + return -1 + if "__main__" == __name__: sys.exit(main(sys.argv)) diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/del_archives.py b/components/clp-package-utils/clp_package_utils/scripts/native/del_archives.py new file mode 100644 index 000000000..735bf299d --- /dev/null +++ b/components/clp-package-utils/clp_package_utils/scripts/native/del_archives.py @@ -0,0 +1,139 @@ +import argparse +import logging +import shutil +import sys +from contextlib import closing +from pathlib import Path +from typing import List + +from clp_py_utils.clp_config import Database +from clp_py_utils.sql_adapter import SQL_Adapter + +from clp_package_utils.general import ( + CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, + get_clp_home, + load_config_file, +) + +logger = logging.getLogger(__file__) + + +def main(argv): + clp_home = get_clp_home() + default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH + + args_parser = argparse.ArgumentParser( + description="Deletes archives that fall within the specified time range." + ) + args_parser.add_argument( + "--config", + "-c", + required=True, + default=str(default_config_file_path), + help="CLP configuration file.", + ) + args_parser.add_argument( + "begin_ts", + type=int, + help="Time-range lower-bound (inclusive) as milliseconds from the UNIX epoch.", + ) + args_parser.add_argument( + "end_ts", + type=int, + help="Time-range upper-bound (include) as milliseconds from the UNIX epoch.", + ) + parsed_args = args_parser.parse_args(argv[1:]) + + # Validate and load config file + config_file_path = Path(parsed_args.config) + try: + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) + clp_config.validate_logs_dir() + except: + logger.exception("Failed to load config.") + return -1 + + database_config = clp_config.database + archives_dir = clp_config.archive_output.directory + if not archives_dir.exists(): + logger.error("`archive_output.directory` doesn't exist.") + return -1 + + return _delete_archives( + archives_dir, + database_config, + parsed_args.begin_ts, + parsed_args.end_ts, + ) + + +def _delete_archives( + archives_dir: Path, + database_config: Database, + begin_ts: int, + end_ts: int, +) -> int: + """ + Deletes all archives where `begin_ts <= archive.begin_timestamp` and + `archive.end_timestamp <= end_ts` from both the metadata database and disk. + :param archives_dir: + :param database_config: + :param begin_ts: + :param end_ts: + :return: 0 on success, -1 otherwise. + """ + + archive_ids: List[str] + logger.info("Starting to delete archives from the database.") + try: + sql_adapter = SQL_Adapter(database_config) + clp_db_connection_params = database_config.get_clp_connection_params_and_type(True) + table_prefix = clp_db_connection_params["table_prefix"] + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + db_cursor.execute( + f""" + DELETE FROM `{table_prefix}archives` + WHERE begin_timestamp >= %s AND end_timestamp <= %s + RETURNING id + """, + (begin_ts, end_ts), + ) + results = db_cursor.fetchall() + + if 0 == len(results): + logger.info("No archives (exclusively) within the specified time range.") + return 0 + + archive_ids = [result["id"] for result in results] + db_cursor.execute( + f""" + DELETE FROM `{table_prefix}files` + WHERE archive_id in ({', '.join(['%s'] * len(archive_ids))}) + """, + archive_ids, + ) + db_conn.commit() + except Exception: + logger.exception("Failed to delete archives from the database. Aborting deletion.") + return -1 + + logger.info(f"Finished deleting archives from the database.") + + for archive_id in archive_ids: + archive_path = archives_dir / archive_id + if not archive_path.is_dir(): + logger.warning(f"Archive {archive_id} is not a directory. Skipping deletion.") + continue + + logger.info(f"Deleting archive {archive_id} from disk.") + shutil.rmtree(archive_path) + + logger.info(f"Finished deleting archives from disk.") + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/search.py b/components/clp-package-utils/clp_package_utils/scripts/native/search.py index 844630d3f..d166cf35f 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/search.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/search.py @@ -4,70 +4,29 @@ import asyncio import ipaddress import logging -import multiprocessing import pathlib import socket import sys -import time -from contextlib import closing import msgpack import pymongo -from clp_py_utils.clp_config import Database, ResultsCache, SEARCH_JOBS_TABLE_NAME +from clp_py_utils.clp_config import Database, ResultsCache from clp_py_utils.sql_adapter import SQL_Adapter -from job_orchestration.scheduler.constants import SearchJobStatus -from job_orchestration.scheduler.job_config import AggregationConfig, SearchConfig +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.job_config import AggregationConfig, SearchJobConfig from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, get_clp_home, - validate_and_load_config_file, + load_config_file, +) +from clp_package_utils.scripts.native.utils import ( + run_function_in_process, + submit_query_job, + wait_for_query_job, ) -# Setup logging -# Create logger logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) - - -async def run_function_in_process(function, *args, initializer=None, init_args=None): - """ - Runs the given function in a separate process wrapped in a *cancellable* - asyncio task. This is necessary because asyncio's multiprocessing process - cannot be cancelled once it's started. - :param function: Method to run - :param args: Arguments for the method - :param initializer: Initializer for each process in the pool - :param init_args: Arguments for the initializer - :return: Return value of the method - """ - pool = multiprocessing.Pool(1, initializer, init_args) - - loop = asyncio.get_event_loop() - fut = loop.create_future() - - def process_done_callback(obj): - loop.call_soon_threadsafe(fut.set_result, obj) - - def process_error_callback(err): - loop.call_soon_threadsafe(fut.set_exception, err) - - pool.apply_async( - function, args, callback=process_done_callback, error_callback=process_error_callback - ) - - try: - return await fut - except asyncio.CancelledError: - pass - finally: - pool.terminate() - pool.close() def create_and_monitor_job_in_db( @@ -83,7 +42,7 @@ def create_and_monitor_job_in_db( do_count_aggregation: bool | None, count_by_time_bucket_size: int | None, ): - search_config = SearchConfig( + search_config = SearchJobConfig( query_string=wildcard_query, begin_timestamp=begin_timestamp, end_timestamp=end_timestamp, @@ -106,47 +65,22 @@ def create_and_monitor_job_in_db( search_config.tags = tag_list sql_adapter = SQL_Adapter(db_config) - with closing(sql_adapter.create_connection(True)) as db_conn, closing( - db_conn.cursor(dictionary=True) - ) as db_cursor: - # Create job - db_cursor.execute( - f"INSERT INTO `{SEARCH_JOBS_TABLE_NAME}` (`search_config`) VALUES (%s)", - (msgpack.packb(search_config.dict()),), - ) - db_conn.commit() - job_id = db_cursor.lastrowid + job_id = submit_query_job(sql_adapter, search_config, QueryJobType.SEARCH_OR_AGGREGATION) + job_status = wait_for_query_job(sql_adapter, job_id) - # Wait for the job to be marked complete - while True: - db_cursor.execute( - f"SELECT `status` FROM `{SEARCH_JOBS_TABLE_NAME}` WHERE `id` = {job_id}" - ) - # There will only ever be one row since it's impossible to have more than one job with - # the same ID - new_status = db_cursor.fetchall()[0]["status"] - db_conn.commit() - if new_status in ( - SearchJobStatus.SUCCEEDED, - SearchJobStatus.FAILED, - SearchJobStatus.CANCELLED, - ): - break - - time.sleep(0.5) + if do_count_aggregation is None and count_by_time_bucket_size is None: + return + with pymongo.MongoClient(results_cache.get_uri()) as client: + search_results_collection = client[results_cache.db_name][str(job_id)] + if do_count_aggregation is not None: + for document in search_results_collection.find(): + print(f"tags: {document['group_tags']} count: {document['records'][0]['count']}") + elif count_by_time_bucket_size is not None: + for document in search_results_collection.find(): + print(f"timestamp: {document['timestamp']} count: {document['count']}") - if do_count_aggregation is None and count_by_time_bucket_size is None: - return - with pymongo.MongoClient(results_cache.get_uri()) as client: - search_results_collection = client[results_cache.db_name][str(job_id)] - if do_count_aggregation is not None: - for document in search_results_collection.find(): - print( - f"tags: {document['group_tags']} count: {document['records'][0]['count']}" - ) - elif count_by_time_bucket_size is not None: - for document in search_results_collection.find(): - print(f"timestamp: {document['timestamp']} count: {document['count']}") + if job_status != QueryJobStatus.SUCCEEDED: + logger.error(f"job {job_id} finished with unexpected status: {job_status}") async def worker_connection_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): @@ -328,9 +262,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() except: logger.exception("Failed to load config.") diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/utils.py b/components/clp-package-utils/clp_package_utils/scripts/native/utils.py new file mode 100644 index 000000000..2f5c51a11 --- /dev/null +++ b/components/clp-package-utils/clp_package_utils/scripts/native/utils.py @@ -0,0 +1,98 @@ +import asyncio +import multiprocessing +import time +from contextlib import closing + +import msgpack +from clp_py_utils.clp_config import ( + QUERY_JOBS_TABLE_NAME, +) +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.scheduler_data import QueryJobConfig + + +async def run_function_in_process(function, *args, initializer=None, init_args=None): + """ + Runs the given function in a separate process wrapped in a *cancellable* + asyncio task. This is necessary because asyncio's multiprocessing process + cannot be cancelled once it's started. + :param function: Method to run + :param args: Arguments for the method + :param initializer: Initializer for each process in the pool + :param init_args: Arguments for the initializer + :return: Return value of the method + """ + pool = multiprocessing.Pool(1, initializer, init_args) + + loop = asyncio.get_event_loop() + fut = loop.create_future() + + def process_done_callback(obj): + loop.call_soon_threadsafe(fut.set_result, obj) + + def process_error_callback(err): + loop.call_soon_threadsafe(fut.set_exception, err) + + pool.apply_async( + function, args, callback=process_done_callback, error_callback=process_error_callback + ) + + try: + return await fut + except asyncio.CancelledError: + pass + finally: + pool.terminate() + pool.close() + + +def submit_query_job( + sql_adapter: SQL_Adapter, job_config: QueryJobConfig, job_type: QueryJobType +) -> int: + """ + Submits a query job. + :param sql_adapter: + :param job_config: + :param job_type: + :return: The job's ID. + """ + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + # Create job + db_cursor.execute( + f"INSERT INTO `{QUERY_JOBS_TABLE_NAME}` (`job_config`, `type`) VALUES (%s, %s)", + (msgpack.packb(job_config.dict()), job_type), + ) + db_conn.commit() + return db_cursor.lastrowid + + +def wait_for_query_job(sql_adapter: SQL_Adapter, job_id: int) -> QueryJobStatus: + """ + Waits for the query job with the given ID to complete. + :param sql_adapter: + :param job_id: + :return: The job's status on completion. + """ + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + # Wait for the job to be marked complete + while True: + db_cursor.execute( + f"SELECT `status` FROM `{QUERY_JOBS_TABLE_NAME}` WHERE `id` = {job_id}" + ) + # There will only ever be one row since it's impossible to have more than one job with + # the same ID + new_status = QueryJobStatus(db_cursor.fetchall()[0]["status"]) + db_conn.commit() + if new_status in ( + QueryJobStatus.SUCCEEDED, + QueryJobStatus.FAILED, + QueryJobStatus.CANCELLED, + ): + return new_status + + time.sleep(0.5) diff --git a/components/clp-package-utils/clp_package_utils/scripts/search.py b/components/clp-package-utils/clp_package_utils/scripts/search.py index 2f2450430..beb7fb0b0 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/search.py +++ b/components/clp-package-utils/clp_package_utils/scripts/search.py @@ -10,22 +10,17 @@ from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, + dump_container_config, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, ) -# Setup logging -# Create logger logger = logging.getLogger(__file__) -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) def main(argv): @@ -70,52 +65,32 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() # Validate and load necessary credentials - validate_and_load_db_credentials_file(clp_config, clp_home, True) + validate_and_load_db_credentials_file(clp_config, clp_home, False) except: logger.exception("Failed to load config.") return -1 - container_name = f"clp-search-{str(uuid.uuid4())[-4:]}" + container_name = generate_container_name(str(JobType.SEARCH)) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on - necessary_mounts = [mounts.logs_dir] - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - container_start_cmd.append(clp_config.execution_container) + necessary_mounts = [mounts.clp_home, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off search_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.search", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), parsed_args.wildcard_query, ] # fmt: on @@ -142,7 +117,7 @@ def main(argv): subprocess.run(cmd, check=True) # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 diff --git a/components/clp-package-utils/clp_package_utils/scripts/start_clp.py b/components/clp-package-utils/clp_package_utils/scripts/start_clp.py index c90671f46..8097929f1 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/start_clp.py +++ b/components/clp-package-utils/clp_package_utils/scripts/start_clp.py @@ -8,8 +8,8 @@ import subprocess import sys import time -import typing import uuid +from typing import Any, Dict, List, Optional import yaml from clp_py_utils.clp_config import ( @@ -21,13 +21,14 @@ COMPRESSION_WORKER_COMPONENT_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + QUERY_JOBS_TABLE_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, RESULTS_CACHE_COMPONENT_NAME, - SEARCH_JOBS_TABLE_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, ) from job_orchestration.scheduler.constants import QueueName @@ -44,11 +45,12 @@ get_clp_home, is_container_exited, is_container_running, - validate_and_load_config_file, + load_config_file, validate_and_load_db_credentials_file, validate_and_load_queue_credentials_file, validate_and_load_redis_credentials_file, validate_db_config, + validate_log_viewer_webui_config, validate_queue_config, validate_redis_config, validate_reducer_config, @@ -57,15 +59,7 @@ validate_worker_config, ) -# Setup logging -# Create logger -logger = logging.getLogger("clp") -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) +logger = logging.getLogger(__file__) def container_exists(container_name): @@ -87,6 +81,21 @@ def append_docker_port_settings_for_host_ips( cmd.append(f"{ip}:{host_port}:{container_port}") +def chown_recursively( + path: pathlib.Path, + user_id: int, + group_id: int, +): + """ + Recursively changes the owner of the given path to the given user ID and group ID. + :param path: + :param user_id: + :param group_id: + """ + chown_cmd = ["chown", "--recursive", f"{user_id}:{group_id}", str(path)] + subprocess.run(chown_cmd, stdout=subprocess.DEVNULL, check=True) + + def wait_for_container_cmd(container_name: str, cmd_to_run: [str], timeout: int): container_exec_cmd = ["docker", "exec", container_name] cmd = container_exec_cmd + cmd_to_run @@ -232,6 +241,54 @@ def create_db_tables( logger.info(f"Created {component_name} tables.") +def create_results_cache_indices( + instance_id: str, + clp_config: CLPConfig, + container_clp_config: CLPConfig, + mounts: CLPDockerMounts, +): + component_name = RESULTS_CACHE_COMPONENT_NAME + logger.info(f"Creating {component_name} indices...") + + container_name = f"clp-{component_name}-indices-creator-{instance_id}" + + clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" + # fmt: off + container_start_cmd = [ + "docker", "run", + "-i", + "--network", "host", + "--rm", + "--name", container_name, + "--log-driver", "local", + "-e", f"PYTHONPATH={clp_site_packages_dir}", + "-u", f"{os.getuid()}:{os.getgid()}", + ] + # fmt: on + necessary_mounts = [mounts.clp_home, mounts.data_dir, mounts.logs_dir] + for mount in necessary_mounts: + if mount: + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) + container_start_cmd.append(clp_config.execution_container) + + clp_py_utils_dir = clp_site_packages_dir / "clp_py_utils" + # fmt: off + create_tables_cmd = [ + "python3", + str(clp_py_utils_dir / "create-results-cache-indices.py"), + "--uri", container_clp_config.results_cache.get_uri(), + "--stream-collection", container_clp_config.results_cache.stream_collection_name, + ] + # fmt: on + + cmd = container_start_cmd + create_tables_cmd + logger.debug(" ".join(cmd)) + subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + + logger.info(f"Created {component_name} indices.") + + def start_queue(instance_id: str, clp_config: CLPConfig): component_name = QUEUE_COMPONENT_NAME logger.info(f"Starting {component_name}...") @@ -268,6 +325,19 @@ def start_queue(instance_id: str, clp_config: CLPConfig): DockerMount(DockerMountType.BIND, queue_logs_dir, rabbitmq_logs_dir), ] rabbitmq_pid_file_path = pathlib.Path("/") / "tmp" / "rabbitmq.pid" + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "rabbitmq" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(queue_logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", @@ -277,7 +347,7 @@ def start_queue(instance_id: str, clp_config: CLPConfig): # Override RABBITMQ_LOGS since the image sets it to *only* log to stdout "-e", f"RABBITMQ_LOGS={rabbitmq_logs_dir / log_filename}", "-e", f"RABBITMQ_PID_FILE={rabbitmq_pid_file_path}", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user ] # fmt: on append_docker_port_settings_for_host_ips( @@ -330,13 +400,27 @@ def start_redis(instance_id: str, clp_config: CLPConfig, conf_dir: pathlib.Path) ), DockerMount(DockerMountType.BIND, redis_data_dir, pathlib.Path("/") / "data"), ] + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "redis" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(redis_data_dir, default_container_user_id, default_container_group_id) + chown_recursively(redis_logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", "-d", "--name", container_name, "--log-driver", "local", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user, ] # fmt: on for mount in mounts: @@ -350,6 +434,19 @@ def start_redis(instance_id: str, clp_config: CLPConfig, conf_dir: pathlib.Path) cmd.append(str(config_file_path)) subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + # fmt: off + redis_ping_cmd = [ + "redis-cli", + "-h", "127.0.0.1", + "-p", "6379", + "-a", clp_config.redis.password, + "PING" + ] + # fmt: on + + if not wait_for_container_cmd(container_name, redis_ping_cmd, 30): + raise EnvironmentError(f"{component_name} did not initialize in time") + logger.info(f"Started {component_name}.") @@ -376,13 +473,27 @@ def start_results_cache(instance_id: str, clp_config: CLPConfig, conf_dir: pathl DockerMount(DockerMountType.BIND, data_dir, pathlib.Path("/") / "data" / "db"), DockerMount(DockerMountType.BIND, logs_dir, pathlib.Path("/") / "var" / "log" / "mongodb"), ] + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "mongodb" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(data_dir, default_container_user_id, default_container_group_id) + chown_recursively(logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", "-d", "--name", container_name, "--log-driver", "local", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user, ] # fmt: on for mount in mounts: @@ -416,15 +527,15 @@ def start_compression_scheduler( ) -def start_search_scheduler( +def start_query_scheduler( instance_id: str, clp_config: CLPConfig, container_clp_config: CLPConfig, mounts: CLPDockerMounts, ): - module_name = "job_orchestration.scheduler.search.search_scheduler" + module_name = "job_orchestration.scheduler.query.query_scheduler" generic_start_scheduler( - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, module_name, instance_id, clp_config, @@ -474,10 +585,10 @@ def generic_start_scheduler( "-e", ( f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@" f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/" - f"{container_clp_config.redis.search_backend_database}" + f"{container_clp_config.redis.query_backend_database}" ), "-e", f"CLP_LOGS_DIR={container_logs_dir}", - "-e", f"CLP_LOGGING_LEVEL={clp_config.search_scheduler.logging_level}", + "-e", f"CLP_LOGGING_LEVEL={clp_config.query_scheduler.logging_level}", "-u", f"{os.getuid()}:{os.getgid()}", "--mount", str(mounts.clp_home), ] @@ -526,29 +637,40 @@ def start_compression_worker( clp_config.redis.compression_backend_database, num_cpus, mounts, + None, + None, ) -def start_search_worker( +def start_query_worker( instance_id: str, clp_config: CLPConfig, container_clp_config: CLPConfig, num_cpus: int, mounts: CLPDockerMounts, ): - celery_method = "job_orchestration.executor.search" - celery_route = f"{QueueName.SEARCH}" + celery_method = "job_orchestration.executor.query" + celery_route = f"{QueueName.QUERY}" + + query_worker_mount = [mounts.stream_output_dir] + query_worker_env = { + "CLP_STREAM_OUTPUT_DIR": container_clp_config.stream_output.directory, + "CLP_STREAM_COLLECTION_NAME": clp_config.results_cache.stream_collection_name, + } + generic_start_worker( - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, instance_id, clp_config, - clp_config.search_worker, + clp_config.query_worker, container_clp_config, celery_method, celery_route, - clp_config.redis.search_backend_database, + clp_config.redis.query_backend_database, num_cpus, mounts, + query_worker_env, + query_worker_mount, ) @@ -563,6 +685,8 @@ def generic_start_worker( redis_database: int, num_cpus: int, mounts: CLPDockerMounts, + worker_specific_env: Dict[str, Any], + worker_specific_mount: List[Optional[DockerMount]], ): logger.info(f"Starting {component_name}...") @@ -578,6 +702,7 @@ def generic_start_worker( # Create necessary directories clp_config.archive_output.directory.mkdir(parents=True, exist_ok=True) + clp_config.stream_output.directory.mkdir(parents=True, exist_ok=True) clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" # fmt: off @@ -605,19 +730,28 @@ def generic_start_worker( "-e", f"CLP_LOGGING_LEVEL={worker_config.logging_level}", "-e", f"CLP_STORAGE_ENGINE={clp_config.package.storage_engine}", "-u", f"{os.getuid()}:{os.getgid()}", - "--mount", str(mounts.clp_home), ] + if worker_specific_env: + for env_name, env_value in worker_specific_env.items(): + container_start_cmd.append("-e") + container_start_cmd.append(f"{env_name}={env_value}") + # fmt: on necessary_mounts = [ + mounts.clp_home, mounts.data_dir, mounts.logs_dir, mounts.archives_output_dir, mounts.input_logs_dir, ] + if worker_specific_mount: + necessary_mounts.extend(worker_specific_mount) + for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) + if not mount: + raise ValueError(f"Required mount configuration is empty: {necessary_mounts}") + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) container_start_cmd.append(clp_config.execution_container) worker_cmd = [ @@ -643,13 +777,13 @@ def generic_start_worker( logger.info(f"Started {component_name}.") -def update_meteor_settings( +def update_settings_object( parent_key_prefix: str, - settings: typing.Dict[str, typing.Any], - updates: typing.Dict[str, typing.Any], + settings: Dict[str, Any], + updates: Dict[str, Any], ): """ - Recursively updates the given Meteor settings object with the values from `updates`. + Recursively updates the given settings object with the values from `updates`. :param parent_key_prefix: The prefix for keys at this level in the settings dictionary. :param settings: The settings to update. @@ -661,11 +795,25 @@ def update_meteor_settings( error_msg = f"{parent_key_prefix}{key} is not a valid configuration key for the webui." raise ValueError(error_msg) if isinstance(value, dict): - update_meteor_settings(f"{parent_key_prefix}{key}.", settings[key], value) + update_settings_object(f"{parent_key_prefix}{key}.", settings[key], value) else: settings[key] = updates[key] +def read_and_update_settings_json(settings_file_path: pathlib.Path, updates: Dict[str, Any]): + """ + Reads and updates a settings JSON file. + + :param settings_file_path: + :param updates: + """ + with open(settings_file_path, "r") as settings_json_file: + settings_object = json.loads(settings_json_file.read()) + update_settings_object("", settings_object, updates) + + return settings_object + + def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts): component_name = WEBUI_COMPONENT_NAME logger.info(f"Starting {component_name}...") @@ -675,10 +823,9 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts return webui_logs_dir = clp_config.logs_directory / component_name - node_path = str( - CONTAINER_CLP_HOME / "var" / "www" / "programs" / "server" / "npm" / "node_modules" - ) - settings_json_path = get_clp_home() / "var" / "www" / "settings.json" + container_webui_dir = CONTAINER_CLP_HOME / "var" / "www" / "webui" + node_path = str(container_webui_dir / "programs" / "server" / "npm" / "node_modules") + settings_json_path = get_clp_home() / "var" / "www" / "webui" / "settings.json" validate_webui_config(clp_config, webui_logs_dir, settings_json_path) @@ -686,8 +833,8 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts webui_logs_dir.mkdir(exist_ok=True, parents=True) container_webui_logs_dir = pathlib.Path("/") / "var" / "log" / component_name - with open(settings_json_path, "r") as settings_json_file: - meteor_settings = json.loads(settings_json_file.read()) + + # Read and update settings.json meteor_settings_updates = { "private": { "SqlDbHost": clp_config.database.host, @@ -696,13 +843,16 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts "SqlDbClpArchivesTableName": f"{CLP_METADATA_TABLE_PREFIX}archives", "SqlDbClpFilesTableName": f"{CLP_METADATA_TABLE_PREFIX}files", "SqlDbCompressionJobsTableName": COMPRESSION_JOBS_TABLE_NAME, - "SqlDbSearchJobsTableName": SEARCH_JOBS_TABLE_NAME, + "SqlDbQueryJobsTableName": QUERY_JOBS_TABLE_NAME, }, "public": { "ClpStorageEngine": clp_config.package.storage_engine, + "LogViewerWebuiUrl": ( + f"http://{clp_config.log_viewer_webui.host}:{clp_config.log_viewer_webui.port}", + ), }, } - update_meteor_settings("", meteor_settings, meteor_settings_updates) + meteor_settings = read_and_update_settings_json(settings_json_path, meteor_settings_updates) # Start container # fmt: off @@ -735,9 +885,86 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts container_cmd.append(clp_config.execution_container) node_cmd = [ - str(CONTAINER_CLP_HOME / "bin" / "node"), - str(CONTAINER_CLP_HOME / "var" / "www" / "launcher.js"), - str(CONTAINER_CLP_HOME / "var" / "www" / "main.js"), + str(CONTAINER_CLP_HOME / "bin" / "node-14"), + str(container_webui_dir / "launcher.js"), + str(container_webui_dir / "main.js"), + ] + cmd = container_cmd + node_cmd + subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + + logger.info(f"Started {component_name}.") + + +def start_log_viewer_webui( + instance_id: str, + clp_config: CLPConfig, + container_clp_config: CLPConfig, + mounts: CLPDockerMounts, +): + component_name = LOG_VIEWER_WEBUI_COMPONENT_NAME + logger.info(f"Starting {component_name}...") + + container_name = f"clp-{component_name}-{instance_id}" + if container_exists(container_name): + return + + container_log_viewer_webui_dir = CONTAINER_CLP_HOME / "var" / "www" / "log_viewer_webui" + node_path = str(container_log_viewer_webui_dir / "server" / "node_modules") + settings_json_path = ( + get_clp_home() / "var" / "www" / "log_viewer_webui" / "server" / "settings.json" + ) + + validate_log_viewer_webui_config(clp_config, settings_json_path) + + # Read, update, and write back settings.json + settings_json_updates = { + "SqlDbHost": clp_config.database.host, + "SqlDbPort": clp_config.database.port, + "SqlDbName": clp_config.database.name, + "SqlDbQueryJobsTableName": QUERY_JOBS_TABLE_NAME, + "MongoDbHost": clp_config.results_cache.host, + "MongoDbPort": clp_config.results_cache.port, + "MongoDbName": clp_config.results_cache.db_name, + "MongoDbStreamFilesCollectionName": clp_config.results_cache.stream_collection_name, + "ClientDir": str(container_log_viewer_webui_dir / "client"), + "StreamFilesDir": str(container_clp_config.stream_output.directory), + "StreamTargetUncompressedSize": container_clp_config.stream_output.target_uncompressed_size, + "LogViewerDir": str(container_log_viewer_webui_dir / "yscope-log-viewer"), + } + settings_json = read_and_update_settings_json(settings_json_path, settings_json_updates) + with open(settings_json_path, "w") as settings_json_file: + settings_json_file.write(json.dumps(settings_json)) + + # Start container + # fmt: off + container_cmd = [ + "docker", "run", + "-d", + "--network", "host", + "--name", container_name, + "--log-driver", "local", + "-e", f"NODE_PATH={node_path}", + "-e", f"HOST={clp_config.log_viewer_webui.host}", + "-e", f"PORT={clp_config.log_viewer_webui.port}", + "-e", f"CLP_DB_USER={clp_config.database.username}", + "-e", f"CLP_DB_PASS={clp_config.database.password}", + "-e", f"NODE_ENV=production", + "-u", f"{os.getuid()}:{os.getgid()}", + ] + # fmt: on + necessary_mounts = [ + mounts.clp_home, + mounts.stream_output_dir, + ] + for mount in necessary_mounts: + if mount: + container_cmd.append("--mount") + container_cmd.append(str(mount)) + container_cmd.append(clp_config.execution_container) + + node_cmd = [ + str(CONTAINER_CLP_HOME / "bin" / "node-22"), + str(container_log_viewer_webui_dir / "server" / "src" / "main.js"), ] cmd = container_cmd + node_cmd subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) @@ -839,14 +1066,15 @@ def main(argv): component_args_parser.add_parser(REDIS_COMPONENT_NAME) component_args_parser.add_parser(RESULTS_CACHE_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_SCHEDULER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_SCHEDULER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_SCHEDULER_COMPONENT_NAME) compression_worker_parser = component_args_parser.add_parser(COMPRESSION_WORKER_COMPONENT_NAME) add_num_workers_argument(compression_worker_parser) - search_worker_parser = component_args_parser.add_parser(SEARCH_WORKER_COMPONENT_NAME) - add_num_workers_argument(search_worker_parser) + query_worker_parser = component_args_parser.add_parser(QUERY_WORKER_COMPONENT_NAME) + add_num_workers_argument(query_worker_parser) reducer_server_parser = component_args_parser.add_parser(REDUCER_COMPONENT_NAME) add_num_workers_argument(reducer_server_parser) component_args_parser.add_parser(WEBUI_COMPONENT_NAME) + component_args_parser.add_parser(LOG_VIEWER_WEBUI_COMPONENT_NAME) parsed_args = args_parser.parse_args(argv[1:]) @@ -864,9 +1092,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) # Validate and load necessary credentials if target in ( @@ -874,8 +1100,9 @@ def main(argv): CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, ): validate_and_load_db_credentials_file(clp_config, clp_home, True) if target in ( @@ -883,9 +1110,9 @@ def main(argv): CONTROLLER_TARGET_NAME, QUEUE_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_queue_credentials_file(clp_config, clp_home, True) if target in ( @@ -893,9 +1120,9 @@ def main(argv): CONTROLLER_TARGET_NAME, REDIS_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_redis_credentials_file(clp_config, clp_home, True) @@ -908,7 +1135,7 @@ def main(argv): if target in ( COMPRESSION_WORKER_COMPONENT_NAME, REDUCER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): num_workers = parsed_args.num_workers else: @@ -945,24 +1172,28 @@ def main(argv): start_redis(instance_id, clp_config, conf_dir) if target in (ALL_TARGET_NAME, RESULTS_CACHE_COMPONENT_NAME): start_results_cache(instance_id, clp_config, conf_dir) + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, RESULTS_CACHE_COMPONENT_NAME): + create_results_cache_indices(instance_id, clp_config, container_clp_config, mounts) if target in ( ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, ): start_compression_scheduler(instance_id, clp_config, container_clp_config, mounts) - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, SEARCH_SCHEDULER_COMPONENT_NAME): - start_search_scheduler(instance_id, clp_config, container_clp_config, mounts) + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, QUERY_SCHEDULER_COMPONENT_NAME): + start_query_scheduler(instance_id, clp_config, container_clp_config, mounts) if target in (ALL_TARGET_NAME, COMPRESSION_WORKER_COMPONENT_NAME): start_compression_worker( instance_id, clp_config, container_clp_config, num_workers, mounts ) - if target in (ALL_TARGET_NAME, SEARCH_WORKER_COMPONENT_NAME): - start_search_worker(instance_id, clp_config, container_clp_config, num_workers, mounts) + if target in (ALL_TARGET_NAME, QUERY_WORKER_COMPONENT_NAME): + start_query_worker(instance_id, clp_config, container_clp_config, num_workers, mounts) if target in (ALL_TARGET_NAME, REDUCER_COMPONENT_NAME): start_reducer(instance_id, clp_config, container_clp_config, num_workers, mounts) if target in (ALL_TARGET_NAME, WEBUI_COMPONENT_NAME): start_webui(instance_id, clp_config, mounts) + if target in (ALL_TARGET_NAME, LOG_VIEWER_WEBUI_COMPONENT_NAME): + start_log_viewer_webui(instance_id, clp_config, container_clp_config, mounts) except Exception as ex: if type(ex) == ValueError: diff --git a/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py b/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py index 307e54236..a55d7a795 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py +++ b/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py @@ -11,12 +11,13 @@ COMPRESSION_WORKER_COMPONENT_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, RESULTS_CACHE_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, ) @@ -25,20 +26,12 @@ get_clp_home, is_container_exited, is_container_running, - validate_and_load_config_file, + load_config_file, validate_and_load_db_credentials_file, validate_and_load_queue_credentials_file, ) -# Setup logging -# Create logger -logger = logging.getLogger("clp") -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(name)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) +logger = logging.getLogger(__file__) def stop_running_container(container_name: str, already_exited_containers: List[str], force: bool): @@ -88,10 +81,11 @@ def main(argv): component_args_parser.add_parser(REDUCER_COMPONENT_NAME) component_args_parser.add_parser(RESULTS_CACHE_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_SCHEDULER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_SCHEDULER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_SCHEDULER_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_WORKER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_WORKER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_WORKER_COMPONENT_NAME) component_args_parser.add_parser(WEBUI_COMPONENT_NAME) + component_args_parser.add_parser(LOG_VIEWER_WEBUI_COMPONENT_NAME) parsed_args = args_parser.parse_args(argv[1:]) @@ -103,12 +97,15 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) # Validate and load necessary credentials - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME): + if target in ( + ALL_TARGET_NAME, + CONTROLLER_TARGET_NAME, + DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + ): validate_and_load_db_credentials_file(clp_config, clp_home, False) if target in ( ALL_TARGET_NAME, @@ -116,8 +113,8 @@ def main(argv): COMPRESSION_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_queue_credentials_file(clp_config, clp_home, False) except: @@ -136,6 +133,9 @@ def main(argv): already_exited_containers = [] force = parsed_args.force + if target in (ALL_TARGET_NAME, LOG_VIEWER_WEBUI_COMPONENT_NAME): + container_name = f"clp-{LOG_VIEWER_WEBUI_COMPONENT_NAME}-{instance_id}" + stop_running_container(container_name, already_exited_containers, force) if target in (ALL_TARGET_NAME, WEBUI_COMPONENT_NAME): container_name = f"clp-{WEBUI_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) @@ -146,14 +146,14 @@ def main(argv): container_config_file_path = logs_dir / f"{container_name}.yml" if container_config_file_path.exists(): container_config_file_path.unlink() - if target in (ALL_TARGET_NAME, SEARCH_WORKER_COMPONENT_NAME): - container_name = f"clp-{SEARCH_WORKER_COMPONENT_NAME}-{instance_id}" + if target in (ALL_TARGET_NAME, QUERY_WORKER_COMPONENT_NAME): + container_name = f"clp-{QUERY_WORKER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) if target in (ALL_TARGET_NAME, COMPRESSION_WORKER_COMPONENT_NAME): container_name = f"clp-{COMPRESSION_WORKER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, SEARCH_SCHEDULER_COMPONENT_NAME): - container_name = f"clp-{SEARCH_SCHEDULER_COMPONENT_NAME}-{instance_id}" + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, QUERY_SCHEDULER_COMPONENT_NAME): + container_name = f"clp-{QUERY_SCHEDULER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) container_config_file_path = logs_dir / f"{container_name}.yml" diff --git a/components/clp-py-utils/clp_py_utils/clp_config.py b/components/clp-py-utils/clp_py_utils/clp_config.py index 58cc84d63..79a94505d 100644 --- a/components/clp-py-utils/clp_py_utils/clp_config.py +++ b/components/clp-py-utils/clp_py_utils/clp_config.py @@ -22,17 +22,18 @@ REDUCER_COMPONENT_NAME = "reducer" RESULTS_CACHE_COMPONENT_NAME = "results_cache" COMPRESSION_SCHEDULER_COMPONENT_NAME = "compression_scheduler" -SEARCH_SCHEDULER_COMPONENT_NAME = "search_scheduler" +QUERY_SCHEDULER_COMPONENT_NAME = "query_scheduler" COMPRESSION_WORKER_COMPONENT_NAME = "compression_worker" -SEARCH_WORKER_COMPONENT_NAME = "search_worker" +QUERY_WORKER_COMPONENT_NAME = "query_worker" WEBUI_COMPONENT_NAME = "webui" +LOG_VIEWER_WEBUI_COMPONENT_NAME = "log_viewer_webui" # Target names ALL_TARGET_NAME = "" CONTROLLER_TARGET_NAME = "controller" -SEARCH_JOBS_TABLE_NAME = "search_jobs" -SEARCH_TASKS_TABLE_NAME = "search_tasks" +QUERY_JOBS_TABLE_NAME = "query_jobs" +QUERY_TASKS_TABLE_NAME = "query_tasks" COMPRESSION_JOBS_TABLE_NAME = "compression_jobs" COMPRESSION_TASKS_TABLE_NAME = "compression_tasks" @@ -153,6 +154,20 @@ def _validate_logging_level(cls, field): ) +def _validate_host(cls, field): + if "" == field: + raise ValueError(f"{cls.__name__}.host cannot be empty.") + + +def _validate_port(cls, field): + min_valid_port = 0 + max_valid_port = 2**16 - 1 + if min_valid_port > field or max_valid_port < field: + raise ValueError( + f"{cls.__name__}.port is not within valid range " f"{min_valid_port}-{max_valid_port}." + ) + + class CompressionScheduler(BaseModel): jobs_poll_delay: float = 0.1 # seconds logging_level: str = "INFO" @@ -163,7 +178,7 @@ def validate_logging_level(cls, field): return field -class SearchScheduler(BaseModel): +class QueryScheduler(BaseModel): host = "localhost" port = 7000 jobs_poll_delay: float = 0.1 # seconds @@ -197,7 +212,7 @@ def validate_logging_level(cls, field): return field -class SearchWorker(BaseModel): +class QueryWorker(BaseModel): logging_level: str = "INFO" @validator("logging_level") @@ -209,7 +224,7 @@ def validate_logging_level(cls, field): class Redis(BaseModel): host: str = "localhost" port: int = 6379 - search_backend_database: int = 0 + query_backend_database: int = 0 compression_backend_database: int = 1 # redis can perform authentication without a username password: typing.Optional[str] @@ -254,7 +269,8 @@ def validate_upsert_interval(cls, field): class ResultsCache(BaseModel): host: str = "localhost" port: int = 27017 - db_name: str = "clp-search" + db_name: str = "clp-query-results" + stream_collection_name: str = "stream-files" @validator("host") def validate_host(cls, field): @@ -268,6 +284,14 @@ def validate_db_name(cls, field): raise ValueError(f"{RESULTS_CACHE_COMPONENT_NAME}.db_name cannot be empty.") return field + @validator("stream_collection_name") + def validate_stream_collection_name(cls, field): + if "" == field: + raise ValueError( + f"{RESULTS_CACHE_COMPONENT_NAME}.stream_collection_name cannot be empty." + ) + return field + def get_uri(self): return f"mongodb://{self.host}:{self.port}/{self.db_name}" @@ -321,6 +345,32 @@ def dump_to_primitive_dict(self): return d +class StreamOutput(BaseModel): + directory: pathlib.Path = pathlib.Path("var") / "data" / "streams" + target_uncompressed_size: int = 128 * 1024 * 1024 + + @validator("directory") + def validate_directory(cls, field): + if "" == field: + raise ValueError("directory can not be empty") + return field + + @validator("target_uncompressed_size") + def validate_target_uncompressed_size(cls, field): + if field <= 0: + raise ValueError("target_uncompressed_size must be greater than 0") + return field + + def make_config_paths_absolute(self, clp_home: pathlib.Path): + self.directory = make_config_path_absolute(clp_home, self.directory) + + def dump_to_primitive_dict(self): + d = self.dict() + # Turn directory (pathlib.Path) into a primitive string + d["directory"] = str(d["directory"]) + return d + + class WebUi(BaseModel): host: str = "localhost" port: int = 4000 @@ -328,19 +378,12 @@ class WebUi(BaseModel): @validator("host") def validate_host(cls, field): - if "" == field: - raise ValueError(f"{WEBUI_COMPONENT_NAME}.host cannot be empty.") + _validate_host(cls, field) return field @validator("port") def validate_port(cls, field): - min_valid_port = 0 - max_valid_port = 2**16 - 1 - if min_valid_port > field or max_valid_port < field: - raise ValueError( - f"{WEBUI_COMPONENT_NAME}.port is not within valid range " - f"{min_valid_port}-{max_valid_port}." - ) + _validate_port(cls, field) return field @validator("logging_level") @@ -349,6 +392,21 @@ def validate_logging_level(cls, field): return field +class LogViewerWebUi(BaseModel): + host: str = "localhost" + port: int = 3000 + + @validator("host") + def validate_host(cls, field): + _validate_host(cls, field) + return field + + @validator("port") + def validate_port(cls, field): + _validate_port(cls, field) + return field + + class CLPConfig(BaseModel): execution_container: typing.Optional[str] @@ -361,13 +419,15 @@ class CLPConfig(BaseModel): reducer: Reducer() = Reducer() results_cache: ResultsCache = ResultsCache() compression_scheduler: CompressionScheduler = CompressionScheduler() - search_scheduler: SearchScheduler = SearchScheduler() + query_scheduler: QueryScheduler = QueryScheduler() compression_worker: CompressionWorker = CompressionWorker() - search_worker: SearchWorker = SearchWorker() + query_worker: QueryWorker = QueryWorker() webui: WebUi = WebUi() + log_viewer_webui: LogViewerWebUi = LogViewerWebUi() credentials_file_path: pathlib.Path = CLP_DEFAULT_CREDENTIALS_FILE_PATH archive_output: ArchiveOutput = ArchiveOutput() + stream_output: StreamOutput = StreamOutput() data_directory: pathlib.Path = pathlib.Path("var") / "data" logs_directory: pathlib.Path = pathlib.Path("var") / "log" @@ -377,6 +437,7 @@ def make_config_paths_absolute(self, clp_home: pathlib.Path): self.input_logs_directory = make_config_path_absolute(clp_home, self.input_logs_directory) self.credentials_file_path = make_config_path_absolute(clp_home, self.credentials_file_path) self.archive_output.make_config_paths_absolute(clp_home) + self.stream_output.make_config_paths_absolute(clp_home) self.data_directory = make_config_path_absolute(clp_home, self.data_directory) self.logs_directory = make_config_path_absolute(clp_home, self.logs_directory) self._os_release_file_path = make_config_path_absolute(clp_home, self._os_release_file_path) @@ -396,6 +457,12 @@ def validate_archive_output_dir(self): except ValueError as ex: raise ValueError(f"archive_output.directory is invalid: {ex}") + def validate_stream_output_dir(self): + try: + validate_path_could_be_dir(self.stream_output.directory) + except ValueError as ex: + raise ValueError(f"stream_output.directory is invalid: {ex}") + def validate_data_dir(self): try: validate_path_could_be_dir(self.data_directory) @@ -463,6 +530,7 @@ def load_redis_credentials_from_file(self): def dump_to_primitive_dict(self): d = self.dict() d["archive_output"] = self.archive_output.dump_to_primitive_dict() + d["stream_output"] = self.stream_output.dump_to_primitive_dict() # Turn paths into primitive strings d["input_logs_directory"] = str(self.input_logs_directory) d["credentials_file_path"] = str(self.credentials_file_path) diff --git a/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py b/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py new file mode 100644 index 000000000..db03e9632 --- /dev/null +++ b/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py @@ -0,0 +1,44 @@ +import argparse +import logging +import sys + +from pymongo import IndexModel, MongoClient + +# Setup logging +# Create logger +logger = logging.getLogger(__file__) +logger.setLevel(logging.INFO) +# Setup console logging +logging_console_handler = logging.StreamHandler() +logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") +logging_console_handler.setFormatter(logging_formatter) +logger.addHandler(logging_console_handler) + + +def main(argv): + args_parser = argparse.ArgumentParser(description="Creates results cache indices for CLP.") + args_parser.add_argument("--uri", required=True, help="URI of the results cache.") + args_parser.add_argument( + "--stream-collection", required=True, help="Collection for stream metadata." + ) + parsed_args = args_parser.parse_args(argv[1:]) + + results_cache_uri = parsed_args.uri + stream_collection_name = parsed_args.stream_collection + + try: + with MongoClient(results_cache_uri) as results_cache_client: + stream_collection = results_cache_client.get_default_database()[stream_collection_name] + + file_split_id_index = IndexModel(["file_split_id"]) + orig_file_id_index = IndexModel(["orig_file_id", "begin_msg_ix", "end_msg_ix"]) + stream_collection.create_indexes([file_split_id_index, orig_file_id_index]) + except Exception: + logger.exception("Failed to create clp results cache indices.") + return -1 + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py b/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py index 2a494c502..1ed727367 100644 --- a/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py +++ b/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py @@ -7,8 +7,8 @@ from job_orchestration.scheduler.constants import ( CompressionJobStatus, CompressionTaskStatus, - SearchJobStatus, - SearchTaskStatus, + QueryJobStatus, + QueryTaskStatus, ) from sql_adapter import SQL_Adapter @@ -16,8 +16,8 @@ COMPRESSION_JOBS_TABLE_NAME, COMPRESSION_TASKS_TABLE_NAME, Database, - SEARCH_JOBS_TABLE_NAME, - SEARCH_TASKS_TABLE_NAME, + QUERY_JOBS_TABLE_NAME, + QUERY_TASKS_TABLE_NAME, ) from clp_py_utils.core import read_yaml_config_file @@ -86,23 +86,25 @@ def main(argv): INDEX `job_id` (`job_id`) USING BTREE, INDEX `TASK_STATUS` (`status`) USING BTREE, INDEX `TASK_START_TIME` (`start_time`) USING BTREE, - CONSTRAINT `compression_tasks` FOREIGN KEY (`job_id`) - REFERENCES `compression_jobs` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION + CONSTRAINT `{COMPRESSION_TASKS_TABLE_NAME}` FOREIGN KEY (`job_id`) + REFERENCES `{COMPRESSION_JOBS_TABLE_NAME}` (`id`) + ON UPDATE NO ACTION ON DELETE NO ACTION ) ROW_FORMAT=DYNAMIC """ ) scheduling_db_cursor.execute( f""" - CREATE TABLE IF NOT EXISTS `{SEARCH_JOBS_TABLE_NAME}` ( + CREATE TABLE IF NOT EXISTS `{QUERY_JOBS_TABLE_NAME}` ( `id` INT NOT NULL AUTO_INCREMENT, - `status` INT NOT NULL DEFAULT '{SearchJobStatus.PENDING}', + `type` INT NOT NULL, + `status` INT NOT NULL DEFAULT '{QueryJobStatus.PENDING}', `creation_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `num_tasks` INT NOT NULL DEFAULT '0', `num_tasks_completed` INT NOT NULL DEFAULT '0', `start_time` DATETIME(3) NULL DEFAULT NULL, `duration` FLOAT NULL DEFAULT NULL, - `search_config` VARBINARY(60000) NOT NULL, + `job_config` VARBINARY(60000) NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `JOB_STATUS` (`status`) USING BTREE ) ROW_FORMAT=DYNAMIC @@ -111,9 +113,9 @@ def main(argv): scheduling_db_cursor.execute( f""" - CREATE TABLE IF NOT EXISTS `{SEARCH_TASKS_TABLE_NAME}` ( + CREATE TABLE IF NOT EXISTS `{QUERY_TASKS_TABLE_NAME}` ( `id` BIGINT NOT NULL AUTO_INCREMENT, - `status` INT NOT NULL DEFAULT '{SearchTaskStatus.PENDING}', + `status` INT NOT NULL DEFAULT '{QueryTaskStatus.PENDING}', `creation_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `start_time` DATETIME(3) NULL DEFAULT NULL, `duration` FLOAT NULL DEFAULT NULL, @@ -123,8 +125,9 @@ def main(argv): INDEX `job_id` (`job_id`) USING BTREE, INDEX `TASK_STATUS` (`status`) USING BTREE, INDEX `TASK_START_TIME` (`start_time`) USING BTREE, - CONSTRAINT `search_tasks` FOREIGN KEY (`job_id`) - REFERENCES `search_jobs` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION + CONSTRAINT `{QUERY_TASKS_TABLE_NAME}` FOREIGN KEY (`job_id`) + REFERENCES `{QUERY_JOBS_TABLE_NAME}` (`id`) + ON UPDATE NO ACTION ON DELETE NO ACTION ) ROW_FORMAT=DYNAMIC """ ) diff --git a/components/core/.clang-format b/components/core/.clang-format index 18195cc31..ff65adbae 100644 --- a/components/core/.clang-format +++ b/components/core/.clang-format @@ -1,81 +1,11 @@ -# yamllint disable-line rule:document-start ---- -ColumnLimit: 100 -IndentWidth: 4 -# yamllint disable-line rule:document-start ---- -Language: "Cpp" -AccessModifierOffset: -4 -AlignAfterOpenBracket: "BlockIndent" -AlignArrayOfStructures: "None" -AlignConsecutiveAssignments: "None" -AlignConsecutiveBitFields: "None" -AlignConsecutiveDeclarations: "None" -AlignConsecutiveMacros: "None" -AlignEscapedNewlines: "DontAlign" -AlignOperands: "Align" -AlignTrailingComments: - Kind: "Never" -AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowBreakBeforeNoexceptSpecifier: "OnlyWithParen" -AllowShortBlocksOnASingleLine: "Always" -AllowShortCaseLabelsOnASingleLine: false -AllowShortCompoundRequirementOnASingleLine: true -AllowShortEnumsOnASingleLine: false -AllowShortFunctionsOnASingleLine: "Inline" -AllowShortIfStatementsOnASingleLine: "Never" -AllowShortLambdasOnASingleLine: "All" -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterReturnType: "None" -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: "Yes" -BinPackArguments: false -BinPackParameters: false -BitFieldColonSpacing: "Both" -BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: "MultiLine" - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterExternBlock: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyNamespace: false - SplitEmptyRecord: false -BreakAfterAttributes: "Never" -BreakBeforeBinaryOperators: "All" -BreakBeforeBraces: "Custom" -BreakBeforeConceptDeclarations: "Always" -BreakBeforeInlineASMColon: "OnlyMultiline" -BreakBeforeTernaryOperators: true -BreakConstructorInitializers: "BeforeColon" -BreakInheritanceList: "BeforeColon" -BreakStringLiterals: true -CompactNamespaces: true -ConstructorInitializerIndentWidth: 8 -ContinuationIndentWidth: 8 -Cpp11BracedListStyle: true -DerivePointerAlignment: false -DisableFormat: false -EmptyLineAfterAccessModifier: "Never" -EmptyLineBeforeAccessModifier: "LogicalBlock" -FixNamespaceComments: true -IncludeBlocks: "Regroup" +BasedOnStyle: "InheritParentConfig" + IncludeCategories: # NOTE: A header is grouped by first matching regex # Library headers. Update when adding new libraries. # NOTE: clang-format retains leading white-space on a line in violation of the YAML spec. - - Regex: "<(absl|antlr4|archive|boost|bsoncxx|catch2|curl|date|fmt|json|log_surgeon|mariadb\ -|mongocxx|msgpack|simdjson|spdlog|sqlite3|string_utils|yaml-cpp|zstd)" + - Regex: "<(absl|antlr4|archive|boost|bsoncxx|catch2|curl|date|fmt|json|log_surgeon|mongocxx\ +|msgpack|mysql|openssl|outcome|regex_utils|simdjson|spdlog|sqlite3|string_utils|yaml-cpp|zstd)" Priority: 3 # C system headers - Regex: "^<.+\\.h>" @@ -86,80 +16,3 @@ IncludeCategories: # Project headers - Regex: "^\".+\"" Priority: 4 -IndentAccessModifiers: false -IndentCaseBlocks: false -IndentCaseLabels: true -IndentExternBlock: "Indent" -IndentGotoLabels: false -IndentPPDirectives: "BeforeHash" -IndentRequiresClause: false -IndentWrappedFunctionNames: false -InsertBraces: true -InsertNewlineAtEOF: true -IntegerLiteralSeparator: - Binary: 4 - BinaryMinDigits: 4 - Decimal: 3 - DecimalMinDigits: 5 - Hex: 4 - HexMinDigits: 4 -KeepEmptyLinesAtTheStartOfBlocks: false -LambdaBodyIndentation: "Signature" -LineEnding: "LF" -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: "None" -PPIndentWidth: -1 -PackConstructorInitializers: "CurrentLine" -PenaltyBreakOpenParenthesis: 25 -PenaltyBreakBeforeFirstCallParameter: 25 -PenaltyReturnTypeOnItsOwnLine: 100 -PointerAlignment: "Left" -QualifierAlignment: "Custom" -QualifierOrder: - - "static" - - "friend" - - "inline" - # constexpr west as explained in https://www.youtube.com/watch?v=z6s6bacI424 - - "constexpr" - - "type" - - "const" - - "volatile" -ReferenceAlignment: "Pointer" -ReflowComments: true -RemoveBracesLLVM: false -RemoveSemicolon: true -RequiresClausePosition: "OwnLine" -RequiresExpressionIndentation: "OuterScope" -SeparateDefinitionBlocks: "Always" -ShortNamespaceLines: 0 -SortIncludes: "CaseInsensitive" -SortUsingDeclarations: "Lexicographic" -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: true -SpaceAroundPointerQualifiers: "Default" -SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: "ControlStatements" -SpaceBeforeRangeBasedForLoopColon: true -SpaceBeforeSquareBrackets: false -SpaceInEmptyBlock: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: false -SpacesInContainerLiterals: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParens: "Custom" -SpacesInParensOptions: - InConditionalStatements: false - InCStyleCasts: false - InEmptyParentheses: false - Other: false -SpacesInSquareBrackets: false -Standard: "Latest" -TabWidth: 4 -UseTab: "Never" diff --git a/components/core/CMakeLists.txt b/components/core/CMakeLists.txt index c8f76baad..1b4fdb1be 100644 --- a/components/core/CMakeLists.txt +++ b/components/core/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.1) +cmake_minimum_required(VERSION 3.16.3) project(CLP LANGUAGES CXX C) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) @@ -51,10 +51,20 @@ endif() # Detect linking mode (static or shared); Default to static. set(CLP_USE_STATIC_LIBS ON CACHE BOOL "Whether to link against static libraries") -if (CLP_USE_STATIC_LIBS AND APPLE) - message(AUTHOR_WARNING "Building with static libraries is unsupported on macOS." - " Switching to shared libraries.") - set(CLP_USE_STATIC_LIBS OFF) +if (CLP_USE_STATIC_LIBS) + if (APPLE) + set(CLP_STATIC_LIBS_UNSUPPORTED_PLATFORM "macOS") + elseif (EXISTS "/etc/centos-release") + set(CLP_STATIC_LIBS_UNSUPPORTED_PLATFORM "CentOS") + endif() + + if (DEFINED CLP_STATIC_LIBS_UNSUPPORTED_PLATFORM) + message( + AUTHOR_WARNING + "Building with static libraries is unsupported on" + " ${CLP_STATIC_LIBS_UNSUPPORTED_PLATFORM}. Switching to shared libraries.") + set(CLP_USE_STATIC_LIBS OFF) + endif() endif () if(CLP_USE_STATIC_LIBS) set(CLP_LIBS_STRING "static") @@ -88,7 +98,7 @@ endif() if(CLP_USE_STATIC_LIBS) set(Boost_USE_STATIC_LIBS ON) endif() -find_package(Boost 1.74 REQUIRED iostreams program_options filesystem system) +find_package(Boost 1.74 REQUIRED iostreams program_options filesystem system regex) if(Boost_FOUND) message(STATUS "Found Boost ${Boost_VERSION}") else() @@ -146,6 +156,14 @@ else() message(FATAL_ERROR "Could not find ${CLP_LIBS_STRING} libraries for CURL") endif() +# Find OpenSSL +find_package(OpenSSL REQUIRED) +if (OPENSSL_FOUND) + message(STATUS "Found OpenSSL (${OPENSSL_VERSION})") +else () + message(FATAL_ERROR "OpenSSL not found") +endif () + # Add log surgeon add_subdirectory(submodules/log-surgeon EXCLUDE_FROM_ALL) @@ -176,7 +194,7 @@ else() endif() # Find and setup msgpack -find_package(msgpack-cxx 6.0.0 REQUIRED) +find_package(msgpack-cxx 7.0.0 REQUIRED) if(msgpack-cxx_FOUND) message(STATUS "Found msgpack-cxx ${msgpack-cxx_VERSION}") else() @@ -190,6 +208,8 @@ add_subdirectory(submodules/abseil-cpp EXCLUDE_FROM_ALL) # Add simdjson add_subdirectory(submodules/simdjson EXCLUDE_FROM_ALL) +find_package(Threads REQUIRED) + # Add yaml-cpp add_subdirectory(submodules/yaml-cpp EXCLUDE_FROM_ALL) @@ -209,6 +229,7 @@ set(sqlite_DYNAMIC_LIBS "dl;m;pthread") include(cmake/Modules/FindLibraryDependencies.cmake) FindDynamicLibraryDependencies(sqlite "${sqlite_DYNAMIC_LIBS}") +add_subdirectory(src/clp/regex_utils) add_subdirectory(src/clp/string_utils) add_subdirectory(src/clp/clg) @@ -220,6 +241,42 @@ add_subdirectory(src/clp_s) add_subdirectory(src/reducer) set(SOURCE_FILES_clp_s_unitTest + src/clp_s/ArchiveReader.cpp + src/clp_s/ArchiveReader.hpp + src/clp_s/ArchiveWriter.cpp + src/clp_s/ArchiveWriter.hpp + src/clp_s/ColumnReader.cpp + src/clp_s/ColumnReader.hpp + src/clp_s/ColumnWriter.cpp + src/clp_s/ColumnWriter.hpp + src/clp_s/DictionaryEntry.cpp + src/clp_s/DictionaryEntry.hpp + src/clp_s/DictionaryWriter.cpp + src/clp_s/DictionaryWriter.hpp + src/clp_s/FileReader.cpp + src/clp_s/FileReader.hpp + src/clp_s/FileWriter.cpp + src/clp_s/FileWriter.hpp + src/clp_s/JsonConstructor.cpp + src/clp_s/JsonConstructor.hpp + src/clp_s/JsonFileIterator.cpp + src/clp_s/JsonFileIterator.hpp + src/clp_s/JsonParser.cpp + src/clp_s/JsonParser.hpp + src/clp_s/PackedStreamReader.cpp + src/clp_s/PackedStreamReader.hpp + src/clp_s/ReaderUtils.cpp + src/clp_s/ReaderUtils.hpp + src/clp_s/Schema.cpp + src/clp_s/Schema.hpp + src/clp_s/SchemaMap.cpp + src/clp_s/SchemaMap.hpp + src/clp_s/SchemaReader.cpp + src/clp_s/SchemaReader.hpp + src/clp_s/SchemaTree.cpp + src/clp_s/SchemaTree.hpp + src/clp_s/SchemaWriter.cpp + src/clp_s/SchemaWriter.hpp src/clp_s/search/AndExpr.cpp src/clp_s/search/AndExpr.hpp src/clp_s/search/BooleanLiteral.cpp @@ -252,14 +309,31 @@ set(SOURCE_FILES_clp_s_unitTest src/clp_s/search/StringLiteral.hpp src/clp_s/search/Transformation.hpp src/clp_s/search/Value.hpp - src/clp_s/SchemaTree.hpp + src/clp_s/TimestampDictionaryReader.cpp + src/clp_s/TimestampDictionaryReader.hpp + src/clp_s/TimestampDictionaryWriter.cpp + src/clp_s/TimestampDictionaryWriter.hpp + src/clp_s/TimestampEntry.cpp + src/clp_s/TimestampEntry.hpp src/clp_s/TimestampPattern.cpp src/clp_s/TimestampPattern.hpp src/clp_s/Utils.cpp src/clp_s/Utils.hpp + src/clp_s/VariableDecoder.cpp + src/clp_s/VariableDecoder.hpp + src/clp_s/VariableEncoder.cpp + src/clp_s/VariableEncoder.hpp + src/clp_s/ZstdCompressor.cpp + src/clp_s/ZstdCompressor.hpp + src/clp_s/ZstdDecompressor.cpp + src/clp_s/ZstdDecompressor.hpp ) set(SOURCE_FILES_unitTest + src/clp/Array.hpp + src/clp/aws/AwsAuthenticationSigner.cpp + src/clp/aws/AwsAuthenticationSigner.hpp + src/clp/aws/constants.hpp src/clp/BufferedFileReader.cpp src/clp/BufferedFileReader.hpp src/clp/BufferReader.cpp @@ -282,6 +356,8 @@ set(SOURCE_FILES_unitTest src/clp/CurlDownloadHandler.cpp src/clp/CurlDownloadHandler.hpp src/clp/CurlEasyHandle.hpp + src/clp/CurlGlobalInstance.cpp + src/clp/CurlGlobalInstance.hpp src/clp/CurlOperationFailed.hpp src/clp/CurlStringList.hpp src/clp/database_utils.cpp @@ -299,15 +375,25 @@ set(SOURCE_FILES_unitTest src/clp/ffi/encoding_methods.hpp src/clp/ffi/encoding_methods.inc src/clp/ffi/ir_stream/byteswap.hpp + src/clp/ffi/ir_stream/Deserializer.hpp src/clp/ffi/ir_stream/decoding_methods.cpp src/clp/ffi/ir_stream/decoding_methods.hpp src/clp/ffi/ir_stream/decoding_methods.inc src/clp/ffi/ir_stream/encoding_methods.cpp src/clp/ffi/ir_stream/encoding_methods.hpp + src/clp/ffi/ir_stream/IrUnitHandlerInterface.hpp + src/clp/ffi/ir_stream/IrUnitType.hpp + src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp + src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp src/clp/ffi/ir_stream/protocol_constants.hpp + src/clp/ffi/ir_stream/Serializer.cpp + src/clp/ffi/ir_stream/Serializer.hpp + src/clp/ffi/ir_stream/utils.cpp + src/clp/ffi/ir_stream/utils.hpp + src/clp/ffi/KeyValuePairLogEvent.cpp + src/clp/ffi/KeyValuePairLogEvent.hpp src/clp/ffi/SchemaTree.cpp src/clp/ffi/SchemaTree.hpp - src/clp/ffi/SchemaTreeNode.hpp src/clp/ffi/search/CompositeWildcardToken.cpp src/clp/ffi/search/CompositeWildcardToken.hpp src/clp/ffi/search/ExactVariableToken.cpp @@ -322,6 +408,13 @@ set(SOURCE_FILES_unitTest src/clp/ffi/search/Subquery.hpp src/clp/ffi/search/WildcardToken.cpp src/clp/ffi/search/WildcardToken.hpp + src/clp/ffi/utils.cpp + src/clp/ffi/utils.hpp + src/clp/ffi/Value.hpp + src/clp/FileDescriptor.cpp + src/clp/FileDescriptor.hpp + src/clp/FileDescriptorReader.cpp + src/clp/FileDescriptorReader.hpp src/clp/FileReader.cpp src/clp/FileReader.hpp src/clp/FileWriter.cpp @@ -335,7 +428,11 @@ set(SOURCE_FILES_unitTest src/clp/GlobalSQLiteMetadataDB.hpp src/clp/Grep.cpp src/clp/Grep.hpp + src/clp/hash_utils.cpp + src/clp/hash_utils.hpp src/clp/ir/constants.hpp + src/clp/ir/EncodedTextAst.cpp + src/clp/ir/EncodedTextAst.hpp src/clp/ir/LogEvent.hpp src/clp/ir/LogEventDeserializer.cpp src/clp/ir/LogEventDeserializer.hpp @@ -379,6 +476,8 @@ set(SOURCE_FILES_unitTest src/clp/Query.hpp src/clp/ReaderInterface.cpp src/clp/ReaderInterface.hpp + src/clp/ReadOnlyMemoryMappedFile.cpp + src/clp/ReadOnlyMemoryMappedFile.hpp src/clp/spdlog_with_specializations.hpp src/clp/SQLiteDB.cpp src/clp/SQLiteDB.hpp @@ -425,11 +524,14 @@ set(SOURCE_FILES_unitTest src/clp/StringReader.hpp src/clp/Thread.cpp src/clp/Thread.hpp + src/clp/time_types.hpp src/clp/TimestampPattern.cpp src/clp/TimestampPattern.hpp src/clp/TraceableException.hpp - src/clp/time_types.hpp + src/clp/TransactionManager.hpp src/clp/type_utils.hpp + src/clp/utf8_utils.cpp + src/clp/utf8_utils.hpp src/clp/Utils.cpp src/clp/Utils.hpp src/clp/VariableDictionaryEntry.cpp @@ -444,25 +546,35 @@ set(SOURCE_FILES_unitTest submodules/sqlite3/sqlite3.h submodules/sqlite3/sqlite3ext.h tests/LogSuppressor.hpp + tests/test-Array.cpp tests/test-BufferedFileReader.cpp + tests/test-clp_s-end_to_end.cpp tests/test-EncodedVariableInterpreter.cpp tests/test-encoding_methods.cpp + tests/test-ffi_IrUnitHandlerInterface.cpp + tests/test-ffi_KeyValuePairLogEvent.cpp tests/test-ffi_SchemaTree.cpp + tests/test-FileDescriptorReader.cpp tests/test-Grep.cpp + tests/test-hash_utils.cpp tests/test-ir_encoding_methods.cpp tests/test-ir_parsing.cpp + tests/test-ir_serializer.cpp tests/test-kql.cpp tests/test-main.cpp tests/test-math_utils.cpp + tests/test-MemoryMappedFile.cpp tests/test-NetworkReader.cpp tests/test-ParserWithUserSchema.cpp tests/test-query_methods.cpp + tests/test-regex_utils.cpp tests/test-Segment.cpp tests/test-SQLiteDB.cpp tests/test-Stopwatch.cpp tests/test-StreamingCompression.cpp tests/test-string_utils.cpp tests/test-TimestampPattern.cpp + tests/test-utf8_utils.cpp tests/test-Utils.cpp ) add_executable(unitTest ${SOURCE_FILES_unitTest} ${SOURCE_FILES_clp_s_unitTest}) @@ -473,16 +585,20 @@ target_include_directories(unitTest target_link_libraries(unitTest PRIVATE absl::flat_hash_map - Boost::filesystem Boost::iostreams Boost::program_options + Boost::filesystem Boost::iostreams Boost::program_options Boost::regex ${CURL_LIBRARIES} fmt::fmt kql log_surgeon::log_surgeon LibArchive::LibArchive MariaDBClient::MariaDBClient + ${MONGOCXX_TARGET} + simdjson spdlog::spdlog + OpenSSL::Crypto ${sqlite_LIBRARY_DEPENDENCIES} ${STD_FS_LIBS} + clp::regex_utils clp::string_utils yaml-cpp::yaml-cpp ZStd::ZStd diff --git a/components/core/cmake/Modules/FindMariaDBClient.cmake b/components/core/cmake/Modules/FindMariaDBClient.cmake index 543f31a6b..5801be2e6 100644 --- a/components/core/cmake/Modules/FindMariaDBClient.cmake +++ b/components/core/cmake/Modules/FindMariaDBClient.cmake @@ -20,6 +20,28 @@ include(cmake/Modules/FindLibraryDependencies.cmake) find_package(PkgConfig) pkg_check_modules(mariadbclient_PKGCONF QUIET "lib${mariadbclient_LIBNAME}") +if(NOT mariadbclient_PKGCONF_FOUND AND APPLE) + execute_process( + COMMAND brew --prefix mariadb-connector-c + RESULT_VARIABLE mariadbclient_BREW_RESULT + OUTPUT_VARIABLE mariadbclient_MACOS_PREFIX + ) + if(NOT mariadbclient_BREW_RESULT EQUAL 0) + message( + FATAL_ERROR + "pkg-config cannot find ${mariadbclient_LIBNAME} and mariadb-connector-c isn't" + " installed via Homebrew" + ) + endif() + string(STRIP "${mariadbclient_MACOS_PREFIX}" mariadbclient_MACOS_PREFIX) + list(PREPEND CMAKE_PREFIX_PATH ${mariadbclient_MACOS_PREFIX}) + pkg_check_modules(mariadbclient_PKGCONF QUIET "lib${mariadbclient_LIBNAME}") +endif() + +if(NOT mariadbclient_PKGCONF_FOUND) + message(FATAL_ERROR "pkg-config cannot find ${mariadbclient_LIBNAME}") +endif() + # Set include directory find_path(MariaDBClient_INCLUDE_DIR mysql.h HINTS ${mariadbclient_PKGCONF_INCLUDEDIR} diff --git a/components/core/cmake/Modules/FindOpenSSL.cmake b/components/core/cmake/Modules/FindOpenSSL.cmake deleted file mode 100644 index 79c3b044b..000000000 --- a/components/core/cmake/Modules/FindOpenSSL.cmake +++ /dev/null @@ -1,534 +0,0 @@ -# NOTE: This is FindOpenSSL.cmake from cmake-3.16.0-rc3. It fixes issues with missing dependencies for the OpenSSL static library. -# This file should be deleted when cmake-3.16 is released. - -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindOpenSSL ------------ - -Find the OpenSSL encryption library. - -Optional COMPONENTS -^^^^^^^^^^^^^^^^^^^ - -This module supports two optional COMPONENTS: ``Crypto`` and ``SSL``. Both -components have associated imported targets, as described below. - -Imported Targets -^^^^^^^^^^^^^^^^ - -This module defines the following :prop_tgt:`IMPORTED` targets: - -``OpenSSL::SSL`` - The OpenSSL ``ssl`` library, if found. -``OpenSSL::Crypto`` - The OpenSSL ``crypto`` library, if found. - -Result Variables -^^^^^^^^^^^^^^^^ - -This module will set the following variables in your project: - -``OPENSSL_FOUND`` - System has the OpenSSL library. If no components are requested it only - requires the crypto library. -``OPENSSL_INCLUDE_DIR`` - The OpenSSL include directory. -``OPENSSL_CRYPTO_LIBRARY`` - The OpenSSL crypto library. -``OPENSSL_CRYPTO_LIBRARIES`` - The OpenSSL crypto library and its dependencies. -``OPENSSL_SSL_LIBRARY`` - The OpenSSL SSL library. -``OPENSSL_SSL_LIBRARIES`` - The OpenSSL SSL library and its dependencies. -``OPENSSL_LIBRARIES`` - All OpenSSL libraries and their dependencies. -``OPENSSL_VERSION`` - This is set to ``$major.$minor.$revision$patch`` (e.g. ``0.9.8s``). - -Hints -^^^^^ - -Set ``OPENSSL_ROOT_DIR`` to the root directory of an OpenSSL installation. -Set ``OPENSSL_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries. -Set ``OPENSSL_MSVC_STATIC_RT`` set ``TRUE`` to choose the MT version of the lib. -#]=======================================================================] - -macro(_OpenSSL_test_and_find_dependencies ssl_library crypto_library) - if((CMAKE_SYSTEM_NAME STREQUAL "Linux") AND - (("${ssl_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") OR - ("${crypto_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$"))) - set(_OpenSSL_has_dependencies TRUE) - find_package(Threads) - else() - set(_OpenSSL_has_dependencies FALSE) - endif() -endmacro() - -function(_OpenSSL_add_dependencies libraries_var library) - if(CMAKE_THREAD_LIBS_INIT) - list(APPEND ${libraries_var} ${CMAKE_THREAD_LIBS_INIT}) - endif() - list(APPEND ${libraries_var} ${CMAKE_DL_LIBS}) - set(${libraries_var} ${${libraries_var}} PARENT_SCOPE) -endfunction() - -function(_OpenSSL_target_add_dependencies target) - if(_OpenSSL_has_dependencies) - set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES Threads::Threads ) - set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS} ) - endif() -endfunction() - -if (UNIX) - find_package(PkgConfig QUIET) - pkg_check_modules(_OPENSSL QUIET openssl) -endif () - -# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES -if(OPENSSL_USE_STATIC_LIBS) - set(_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - if(WIN32) - set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) - else() - set(CMAKE_FIND_LIBRARY_SUFFIXES .a ) - endif() -endif() - -if (WIN32) - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ENV OPENSSL_ROOT_DIR - ) - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - set(_OPENSSL_ROOT_PATHS - "${_programfiles}/OpenSSL" - "${_programfiles}/OpenSSL-Win32" - "${_programfiles}/OpenSSL-Win64" - "C:/OpenSSL/" - "C:/OpenSSL-Win32/" - "C:/OpenSSL-Win64/" - ) - unset(_programfiles) -else () - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - ENV OPENSSL_ROOT_DIR - ) -endif () - -set(_OPENSSL_ROOT_HINTS_AND_PATHS - HINTS ${_OPENSSL_ROOT_HINTS} - PATHS ${_OPENSSL_ROOT_PATHS} - ) - -find_path(OPENSSL_INCLUDE_DIR - NAMES - openssl/ssl.h - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_INCLUDEDIR} - PATH_SUFFIXES - include -) - -if(WIN32 AND NOT CYGWIN) - if(MSVC) - # /MD and /MDd are the standard values - if someone wants to use - # others, the libnames have to change here too - # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b - # enable OPENSSL_MSVC_STATIC_RT to get the libs build /MT (Multithreaded no-DLL) - # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: - # * MD for dynamic-release - # * MDd for dynamic-debug - # * MT for static-release - # * MTd for static-debug - - # Implementation details: - # We are using the libraries located in the VC subdir instead of the parent directory even though : - # libeay32MD.lib is identical to ../libeay32.lib, and - # ssleay32MD.lib is identical to ../ssleay32.lib - # enable OPENSSL_USE_STATIC_LIBS to use the static libs located in lib/VC/static - - if (OPENSSL_MSVC_STATIC_RT) - set(_OPENSSL_MSVC_RT_MODE "MT") - else () - set(_OPENSSL_MSVC_RT_MODE "MD") - endif () - - # Since OpenSSL 1.1, lib names are like libcrypto32MTd.lib and libssl32MTd.lib - if( "${CMAKE_SIZEOF_VOID_P}" STREQUAL "8" ) - set(_OPENSSL_MSVC_ARCH_SUFFIX "64") - else() - set(_OPENSSL_MSVC_ARCH_SUFFIX "32") - endif() - - if(OPENSSL_USE_STATIC_LIBS) - set(_OPENSSL_PATH_SUFFIXES - "lib/VC/static" - "VC/static" - "lib" - ) - else() - set(_OPENSSL_PATH_SUFFIXES - "lib/VC" - "VC" - "lib" - ) - endif () - - find_library(LIB_EAY_DEBUG - NAMES - libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d - libcrypto${_OPENSSL_MSVC_RT_MODE}d - libcryptod - libeay32${_OPENSSL_MSVC_RT_MODE}d - libeay32d - cryptod - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(LIB_EAY_RELEASE - NAMES - libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE} - libcrypto${_OPENSSL_MSVC_RT_MODE} - libcrypto - libeay32${_OPENSSL_MSVC_RT_MODE} - libeay32 - crypto - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(SSL_EAY_DEBUG - NAMES - libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d - libssl${_OPENSSL_MSVC_RT_MODE}d - libssld - ssleay32${_OPENSSL_MSVC_RT_MODE}d - ssleay32d - ssld - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(SSL_EAY_RELEASE - NAMES - libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE} - libssl${_OPENSSL_MSVC_RT_MODE} - libssl - ssleay32${_OPENSSL_MSVC_RT_MODE} - ssleay32 - ssl - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") - set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}") - set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}") - set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}") - - include(SelectLibraryConfigurations) - select_library_configurations(LIB_EAY) - select_library_configurations(SSL_EAY) - - mark_as_advanced(LIB_EAY_LIBRARY_DEBUG LIB_EAY_LIBRARY_RELEASE - SSL_EAY_LIBRARY_DEBUG SSL_EAY_LIBRARY_RELEASE) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} ) - elseif(MINGW) - # same player, for MinGW - set(LIB_EAY_NAMES crypto libeay32) - set(SSL_EAY_NAMES ssl ssleay32) - find_library(LIB_EAY - NAMES - ${LIB_EAY_NAMES} - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib/MinGW" - "lib" - ) - - find_library(SSL_EAY - NAMES - ${SSL_EAY_NAMES} - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib/MinGW" - "lib" - ) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - unset(LIB_EAY_NAMES) - unset(SSL_EAY_NAMES) - else() - # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: - find_library(LIB_EAY - NAMES - libcrypto - libeay32 - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - find_library(SSL_EAY - NAMES - libssl - ssleay32 - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - endif() -else() - - find_library(OPENSSL_SSL_LIBRARY - NAMES - ssl - ssleay32 - ssleay32MD - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - find_library(OPENSSL_CRYPTO_LIBRARY - NAMES - crypto - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - mark_as_advanced(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY) - -endif() - -# compat defines -set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY}) -set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) -_OpenSSL_test_and_find_dependencies("${OPENSSL_SSL_LIBRARY}" "${OPENSSL_CRYPTO_LIBRARY}") -if(_OpenSSL_has_dependencies) - _OpenSSL_add_dependencies( OPENSSL_SSL_LIBRARIES "${OPENSSL_SSL_LIBRARY}" ) - _OpenSSL_add_dependencies( OPENSSL_CRYPTO_LIBRARIES "${OPENSSL_CRYPTO_LIBRARY}" ) -endif() - -function(from_hex HEX DEC) - string(TOUPPER "${HEX}" HEX) - set(_res 0) - string(LENGTH "${HEX}" _strlen) - - while (_strlen GREATER 0) - math(EXPR _res "${_res} * 16") - string(SUBSTRING "${HEX}" 0 1 NIBBLE) - string(SUBSTRING "${HEX}" 1 -1 HEX) - if (NIBBLE STREQUAL "A") - math(EXPR _res "${_res} + 10") - elseif (NIBBLE STREQUAL "B") - math(EXPR _res "${_res} + 11") - elseif (NIBBLE STREQUAL "C") - math(EXPR _res "${_res} + 12") - elseif (NIBBLE STREQUAL "D") - math(EXPR _res "${_res} + 13") - elseif (NIBBLE STREQUAL "E") - math(EXPR _res "${_res} + 14") - elseif (NIBBLE STREQUAL "F") - math(EXPR _res "${_res} + 15") - else() - math(EXPR _res "${_res} + ${NIBBLE}") - endif() - - string(LENGTH "${HEX}" _strlen) - endwhile() - - set(${DEC} ${_res} PARENT_SCOPE) -endfunction() - -if(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h") - file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str - REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*") - - if(openssl_version_str) - # The version number is encoded as 0xMNNFFPPS: major minor fix patch status - # The status gives if this is a developer or prerelease and is ignored here. - # Major, minor, and fix directly translate into the version numbers shown in - # the string. The patch field translates to the single character suffix that - # indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so - # on. - - string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$" - "\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}") - list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR) - list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR) - from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR) - list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX) - from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX) - list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH) - - if (NOT OPENSSL_VERSION_PATCH STREQUAL "00") - from_hex("${OPENSSL_VERSION_PATCH}" _tmp) - # 96 is the ASCII code of 'a' minus 1 - math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96") - unset(_tmp) - # Once anyone knows how OpenSSL would call the patch versions beyond 'z' - # this should be updated to handle that, too. This has not happened yet - # so it is simply ignored here for now. - string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING) - endif () - - set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}") - endif () -endif () - -set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES} ) -list(REMOVE_DUPLICATES OPENSSL_LIBRARIES) - -foreach(_comp IN LISTS OpenSSL_FIND_COMPONENTS) - if(_comp STREQUAL "Crypto") - if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND - (EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR - EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR - EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - ) - set(OpenSSL_${_comp}_FOUND TRUE) - else() - set(OpenSSL_${_comp}_FOUND FALSE) - endif() - elseif(_comp STREQUAL "SSL") - if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND - (EXISTS "${OPENSSL_SSL_LIBRARY}" OR - EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR - EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - ) - set(OpenSSL_${_comp}_FOUND TRUE) - else() - set(OpenSSL_${_comp}_FOUND FALSE) - endif() - else() - message(WARNING "${_comp} is not a valid OpenSSL component") - set(OpenSSL_${_comp}_FOUND FALSE) - endif() -endforeach() -unset(_comp) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OpenSSL - REQUIRED_VARS - OPENSSL_CRYPTO_LIBRARY - OPENSSL_INCLUDE_DIR - VERSION_VAR - OPENSSL_VERSION - HANDLE_COMPONENTS - FAIL_MESSAGE - "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" -) - -mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES) - -if(OPENSSL_FOUND) - if(NOT TARGET OpenSSL::Crypto AND - (EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR - EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR - EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - ) - add_library(OpenSSL::Crypto UNKNOWN IMPORTED) - set_target_properties(OpenSSL::Crypto PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}") - if(EXISTS "${OPENSSL_CRYPTO_LIBRARY}") - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}") - endif() - if(EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - set_property(TARGET OpenSSL::Crypto APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${LIB_EAY_LIBRARY_RELEASE}") - endif() - if(EXISTS "${LIB_EAY_LIBRARY_DEBUG}") - set_property(TARGET OpenSSL::Crypto APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${LIB_EAY_LIBRARY_DEBUG}") - endif() - _OpenSSL_target_add_dependencies(OpenSSL::Crypto) - endif() - - if(NOT TARGET OpenSSL::SSL AND - (EXISTS "${OPENSSL_SSL_LIBRARY}" OR - EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR - EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - ) - add_library(OpenSSL::SSL UNKNOWN IMPORTED) - set_target_properties(OpenSSL::SSL PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}") - if(EXISTS "${OPENSSL_SSL_LIBRARY}") - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${OPENSSL_SSL_LIBRARY}") - endif() - if(EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - set_property(TARGET OpenSSL::SSL APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${SSL_EAY_LIBRARY_RELEASE}") - endif() - if(EXISTS "${SSL_EAY_LIBRARY_DEBUG}") - set_property(TARGET OpenSSL::SSL APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${SSL_EAY_LIBRARY_DEBUG}") - endif() - if(TARGET OpenSSL::Crypto) - set_target_properties(OpenSSL::SSL PROPERTIES - INTERFACE_LINK_LIBRARIES OpenSSL::Crypto) - endif() - _OpenSSL_target_add_dependencies(OpenSSL::SSL) - endif() -endif() - -# Restore the original find library ordering -if(OPENSSL_USE_STATIC_LIBS) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) -endif() diff --git a/components/core/src/clp/Array.hpp b/components/core/src/clp/Array.hpp new file mode 100644 index 000000000..f94892493 --- /dev/null +++ b/components/core/src/clp/Array.hpp @@ -0,0 +1,111 @@ +#ifndef CLP_ARRAY_HPP +#define CLP_ARRAY_HPP + +#include +#include +#include +#include +#include +#include + +namespace clp { +/** + * Class for a runtime fix-sized array. + * @tparam T The type of elements in the array. The type must be default initializable so that this + * class doesn't need to implement a constructor which takes an initializer list. + */ +template +requires(std::is_fundamental_v || std::default_initializable) +class Array { +public: + // Types + using Iterator = T*; + using ConstIterator = T const*; + + // Constructors + // NOLINTNEXTLINE(*-avoid-c-arrays) + explicit Array(size_t size) : m_data{std::make_unique(size)}, m_size{size} { + if constexpr (std::is_fundamental_v) { + memset(m_data.get(), 0, m_size * sizeof(T)); + } + } + + // Disable copy constructor and assignment operator + Array(Array const&) = delete; + auto operator=(Array const&) -> Array& = delete; + + // Default move constructor and assignment operator + Array(Array&&) = default; + auto operator=(Array&&) -> Array& = default; + + // Destructor + ~Array() = default; + + // Methods + /** + * @return Whether the array is empty. + */ + [[nodiscard]] auto empty() const -> bool { return 0 == size(); } + + /** + * @return The size of the array. + */ + [[nodiscard]] auto size() const -> size_t { return m_size; } + + /** + * @return A pointer to the underlying data buffer. + */ + [[nodiscard]] auto data() -> T* { return m_data.get(); } + + /** + * @return A pointer to the underlying data buffer. + */ + [[nodiscard]] auto data() const -> T const* { return m_data.get(); } + + /** + * @param idx + * @return The element at the given index. + * @throw `OperationFailed` if the given index is out of bound. + */ + [[nodiscard]] auto at(size_t idx) -> T& { + assert_is_in_range(idx); + return m_data[idx]; + } + + /** + * @param idx + * @return The element at the given index. + * @throw `OperationFailed` if the given index is out of bound. + */ + [[nodiscard]] auto at(size_t idx) const -> T const& { + assert_is_in_range(idx); + return m_data[idx]; + } + + [[nodiscard]] auto begin() -> Iterator { return m_data.get(); } + + [[nodiscard]] auto end() -> Iterator { return m_data.get() + m_size; } + + [[nodiscard]] auto begin() const -> ConstIterator { return m_data.get(); } + + [[nodiscard]] auto end() const -> ConstIterator { return m_data.get() + m_size; } + +private: + /** + * @param idx + * @throw `std::out_of_range` if the given index is out of bound. + */ + auto assert_is_in_range(size_t idx) -> void { + if (idx >= m_size) { + throw std::out_of_range("clp::Array out-of-range access."); + } + } + + // Variables + // NOLINTNEXTLINE(*-avoid-c-arrays) + std::unique_ptr m_data; + size_t m_size; +}; +} // namespace clp + +#endif // CLP_ARRAY_HPP diff --git a/components/core/src/clp/CurlDownloadHandler.cpp b/components/core/src/clp/CurlDownloadHandler.cpp index 8ec8fbfb6..d1b88758a 100644 --- a/components/core/src/clp/CurlDownloadHandler.cpp +++ b/components/core/src/clp/CurlDownloadHandler.cpp @@ -1,14 +1,26 @@ #include "CurlDownloadHandler.hpp" +#include +#include +#include #include #include +#include +#include #include #include +#include +#include +#include #include +#include + +#include "ErrorCode.hpp" namespace clp { CurlDownloadHandler::CurlDownloadHandler( + std::shared_ptr error_msg_buf, ProgressCallback progress_callback, WriteCallback write_callback, void* arg, @@ -16,8 +28,19 @@ CurlDownloadHandler::CurlDownloadHandler( size_t offset, bool disable_caching, std::chrono::seconds connection_timeout, - std::chrono::seconds overall_timeout -) { + std::chrono::seconds overall_timeout, + std::optional> const& http_header_kv_pairs +) + : m_error_msg_buf{std::move(error_msg_buf)} { + if (nullptr != m_error_msg_buf) { + // Set up error message buffer + // According to the docs (https://curl.se/libcurl/c/CURLOPT_ERRORBUFFER.html), since 7.60.0, + // a successful call to set `CURLOPT_ERRORBUFFER` will initialize the buffer to an empty + // string 7.60.0. Since we require at least 7.68.0, we don't need to clear the provided + // buffer before it's used. + m_easy_handle.set_option(CURLOPT_ERRORBUFFER, m_error_msg_buf->data()); + } + // Set up src url m_easy_handle.set_option(CURLOPT_URL, src_url.data()); @@ -35,16 +58,61 @@ CurlDownloadHandler::CurlDownloadHandler( m_easy_handle.set_option(CURLOPT_TIMEOUT, static_cast(overall_timeout.count())); // Set up http headers + constexpr std::string_view cRangeHeaderName{"range"}; + constexpr std::string_view cCacheControlHeaderName{"cache-control"}; + constexpr std::string_view cPragmaHeaderName{"pragma"}; + std::unordered_set const reserved_headers{ + cRangeHeaderName, + cCacheControlHeaderName, + cPragmaHeaderName + }; if (0 != offset) { - std::string const range{"Range: bytes=" + std::to_string(offset) + "-"}; - m_http_headers.append(range); + m_http_headers.append(fmt::format("{}: bytes={}-", cRangeHeaderName, offset)); } if (disable_caching) { - m_http_headers.append("Cache-Control: no-cache"); - m_http_headers.append("Pragma: no-cache"); + m_http_headers.append(fmt::format("{}: no-cache", cCacheControlHeaderName)); + m_http_headers.append(fmt::format("{}: no-cache", cPragmaHeaderName)); + } + if (http_header_kv_pairs.has_value()) { + for (auto const& [key, value] : http_header_kv_pairs.value()) { + // HTTP header field-name (key) is case-insensitive: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + // Therefore, we convert keys to lowercase for comparison with the reserved keys. + // NOTE: We do not check for duplicate keys due to case insensitivity, leaving duplicate + // handling to the server. + auto lower_key{key}; + std::transform( + lower_key.begin(), + lower_key.end(), + lower_key.begin(), + [](unsigned char c) -> char { + // Implicitly cast the input character into `unsigned char` to avoid UB: + // https://en.cppreference.com/w/cpp/string/byte/tolower + return static_cast(std::tolower(c)); + } + ); + if (reserved_headers.contains(lower_key) || value.ends_with("\r\n")) { + throw CurlOperationFailed( + ErrorCode_Failure, + __FILE__, + __LINE__, + CURLE_BAD_FUNCTION_ARGUMENT, + fmt::format( + "`CurlDownloadHandler` failed to construct with the following " + "invalid header: {}:{}", + key, + value + ) + ); + } + m_http_headers.append(fmt::format("{}: {}", key, value)); + } } if (false == m_http_headers.is_empty()) { m_easy_handle.set_option(CURLOPT_HTTPHEADER, m_http_headers.get_raw_list()); } + + // Set up failure on HTTP error reponse + m_easy_handle.set_option(CURLOPT_FAILONERROR, static_cast(true)); } } // namespace clp diff --git a/components/core/src/clp/CurlDownloadHandler.hpp b/components/core/src/clp/CurlDownloadHandler.hpp index b45f644ca..e7c4b73a8 100644 --- a/components/core/src/clp/CurlDownloadHandler.hpp +++ b/components/core/src/clp/CurlDownloadHandler.hpp @@ -1,9 +1,14 @@ #ifndef CLP_CURLDOWNLOADHANDLER_HPP #define CLP_CURLDOWNLOADHANDLER_HPP +#include #include #include +#include +#include +#include #include +#include #include @@ -18,6 +23,7 @@ namespace clp { class CurlDownloadHandler { public: // Types + using ErrorMsgBuf = std::array; /** * libcurl progress callback. This method must have C linkage. Doc: * https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html @@ -37,6 +43,9 @@ class CurlDownloadHandler { // Constructor /** + * @param error_msg_buf The buffer to store the CURL error message or `nullptr` if it shouldn't + * be stored. + * Doc: https://curl.se/libcurl/c/CURLOPT_ERRORBUFFER.html * @param progress_callback * @param write_callback * @param arg Argument to pass to `progress_callback` and `write_callback` @@ -47,8 +56,12 @@ class CurlDownloadHandler { * Doc: https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT.html * @param overall_timeout Maximum time that the transfer may take. Note that this includes * `connection_timeout`. Doc: https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html + * @param http_header_kv_pairs Key-value pairs representing HTTP headers to pass to the server + * in the download request. Doc: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html + * @throw CurlOperationFailed if an error occurs. */ explicit CurlDownloadHandler( + std::shared_ptr error_msg_buf, ProgressCallback progress_callback, WriteCallback write_callback, void* arg, @@ -56,7 +69,9 @@ class CurlDownloadHandler { size_t offset = 0, bool disable_caching = false, std::chrono::seconds connection_timeout = cDefaultConnectionTimeout, - std::chrono::seconds overall_timeout = cDefaultOverallTimeout + std::chrono::seconds overall_timeout = cDefaultOverallTimeout, + std::optional> const& http_header_kv_pairs + = std::nullopt ); // Disable copy/move constructors/assignment operators @@ -78,6 +93,7 @@ class CurlDownloadHandler { private: CurlEasyHandle m_easy_handle; CurlStringList m_http_headers; + std::shared_ptr m_error_msg_buf; }; } // namespace clp diff --git a/components/core/src/clp/CurlGlobalInstance.cpp b/components/core/src/clp/CurlGlobalInstance.cpp new file mode 100644 index 000000000..7add2afe9 --- /dev/null +++ b/components/core/src/clp/CurlGlobalInstance.cpp @@ -0,0 +1,45 @@ +#include "CurlGlobalInstance.hpp" + +#include + +#include + +#include "CurlOperationFailed.hpp" +#include "ErrorCode.hpp" + +namespace clp { +CurlGlobalInstance::CurlGlobalInstance() { + std::scoped_lock const global_lock{m_ref_count_mutex}; + if (0 == m_ref_count) { + if (auto const err{curl_global_init(CURL_GLOBAL_ALL)}; 0 != err) { + throw CurlOperationFailed( + ErrorCode_Failure, + __FILE__, + __LINE__, + err, + "`curl_global_init` failed." + ); + } + } + ++m_ref_count; +} + +CurlGlobalInstance::~CurlGlobalInstance() { + std::scoped_lock const global_lock{m_ref_count_mutex}; + --m_ref_count; + if (0 == m_ref_count) { +#if defined(__APPLE__) + // NOTE: On macOS, calling `curl_global_init` after `curl_global_cleanup` will fail with + // CURLE_SSL_CONNECT_ERROR. Thus, for now, we skip `deinit` on macOS. Luckily, it is safe to + // call `curl_global_init` multiple times without calling `curl_global_cleanup`. Related + // issues: + // - https://github.com/curl/curl/issues/12525 + // - https://github.com/curl/curl/issues/13805 + // TODO: Remove this conditional logic when the issues are resolved. + return; +#else + curl_global_cleanup(); +#endif + } +} +} // namespace clp diff --git a/components/core/src/clp/CurlGlobalInstance.hpp b/components/core/src/clp/CurlGlobalInstance.hpp new file mode 100644 index 000000000..56ec60ed8 --- /dev/null +++ b/components/core/src/clp/CurlGlobalInstance.hpp @@ -0,0 +1,35 @@ +#ifndef CLP_CURLGLOBALINSTANCE_HPP +#define CLP_CURLGLOBALINSTANCE_HPP + +#include +#include + +namespace clp { +/** + * Class to wrap `libcurl`'s global initialization/de-initialization calls using RAII. Before using + * any `libcurl` functionalities, an instance of this class must be created. Although unnecessasry, + * it can be safely instantiated multiple times; it maintains a static reference count to all + * existing instances and only de-initializes `libcurl`'s global resources when the reference count + * reaches 0. + */ +class CurlGlobalInstance { +public: + // Constructors + CurlGlobalInstance(); + + // Disable copy/move constructors and assignment operators + CurlGlobalInstance(CurlGlobalInstance const&) = delete; + CurlGlobalInstance(CurlGlobalInstance&&) = delete; + auto operator=(CurlGlobalInstance const&) -> CurlGlobalInstance& = delete; + auto operator=(CurlGlobalInstance&&) -> CurlGlobalInstance& = delete; + + // Destructor + ~CurlGlobalInstance(); + +private: + static inline std::mutex m_ref_count_mutex; + static inline size_t m_ref_count{0}; +}; +} // namespace clp + +#endif // CLP_CURLGLOBALINSTANCE_HPP diff --git a/components/core/src/clp/Defs.h b/components/core/src/clp/Defs.h index f4a96ef9b..02eb34258 100644 --- a/components/core/src/clp/Defs.h +++ b/components/core/src/clp/Defs.h @@ -7,19 +7,19 @@ namespace clp { // Types -typedef int64_t epochtime_t; +using epochtime_t = int64_t; constexpr epochtime_t cEpochTimeMin = std::numeric_limits::min(); constexpr epochtime_t cEpochTimeMax = std::numeric_limits::max(); -typedef uint64_t variable_dictionary_id_t; +using variable_dictionary_id_t = uint64_t; constexpr variable_dictionary_id_t cVariableDictionaryIdMax = std::numeric_limits::max(); -typedef int64_t logtype_dictionary_id_t; +using logtype_dictionary_id_t = int64_t; constexpr logtype_dictionary_id_t cLogtypeDictionaryIdMax = std::numeric_limits::max(); -typedef uint16_t archive_format_version_t; +using archive_format_version_t = uint16_t; // This flag is used to maintain two separate streams of archive format // versions: // - Development versions (which can change frequently as necessary) which @@ -28,12 +28,12 @@ typedef uint16_t archive_format_version_t; // as possible) which should not have the flag constexpr archive_format_version_t cArchiveFormatDevVersionFlag = 0x8000; -typedef uint64_t segment_id_t; +using segment_id_t = uint64_t; constexpr segment_id_t cInvalidSegmentId = std::numeric_limits::max(); -typedef int64_t encoded_variable_t; +using encoded_variable_t = int64_t; -typedef uint64_t group_id_t; +using group_id_t = uint64_t; // Constants constexpr char cDefaultConfigFilename[] = ".clp.rc"; diff --git a/components/core/src/clp/DictionaryReader.hpp b/components/core/src/clp/DictionaryReader.hpp index 0499e50eb..694240ad5 100644 --- a/components/core/src/clp/DictionaryReader.hpp +++ b/components/core/src/clp/DictionaryReader.hpp @@ -107,8 +107,8 @@ class DictionaryReader { // Variables bool m_is_open; - FileReader m_dictionary_file_reader; - FileReader m_segment_index_file_reader; + std::unique_ptr m_dictionary_file_reader; + std::unique_ptr m_segment_index_file_reader; #if USE_PASSTHROUGH_COMPRESSION streaming_compression::passthrough::Decompressor m_dictionary_decompressor; streaming_compression::passthrough::Decompressor m_segment_index_decompressor; @@ -133,14 +133,19 @@ void DictionaryReader::open( constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB - open_dictionary_for_reading( - dictionary_path, - segment_index_path, - cDecompressorFileReadBufferCapacity, - m_dictionary_file_reader, - m_dictionary_decompressor, - m_segment_index_file_reader, - m_segment_index_decompressor + m_dictionary_file_reader = make_unique(dictionary_path); + + // Skip header and then open the decompressor + m_dictionary_file_reader->seek_from_begin(sizeof(uint64_t)); + m_dictionary_decompressor.open(*m_dictionary_file_reader, cDecompressorFileReadBufferCapacity); + + m_segment_index_file_reader = make_unique(segment_index_path); + + // Skip header and then open the decompressor + m_segment_index_file_reader->seek_from_begin(sizeof(uint64_t)); + m_segment_index_decompressor.open( + *m_segment_index_file_reader, + cDecompressorFileReadBufferCapacity ); m_is_open = true; @@ -153,9 +158,9 @@ void DictionaryReader::close() { } m_segment_index_decompressor.close(); - m_segment_index_file_reader.close(); + m_segment_index_file_reader.reset(); m_dictionary_decompressor.close(); - m_dictionary_file_reader.close(); + m_dictionary_file_reader.reset(); m_num_segments_read_from_index = 0; m_entries.clear(); @@ -170,7 +175,7 @@ void DictionaryReader::read_new_entries() { } // Read dictionary header - auto num_dictionary_entries = read_dictionary_header(m_dictionary_file_reader); + auto num_dictionary_entries = read_dictionary_header(*m_dictionary_file_reader); // Validate dictionary header if (num_dictionary_entries < m_entries.size()) { @@ -190,7 +195,7 @@ void DictionaryReader::read_new_entries() { } // Read segment index header - auto num_segments = read_segment_index_header(m_segment_index_file_reader); + auto num_segments = read_segment_index_header(*m_segment_index_file_reader); // Validate segment index header if (num_segments < m_num_segments_read_from_index) { diff --git a/components/core/src/clp/DictionaryWriter.hpp b/components/core/src/clp/DictionaryWriter.hpp index e9b6f623c..7cac9d5aa 100644 --- a/components/core/src/clp/DictionaryWriter.hpp +++ b/components/core/src/clp/DictionaryWriter.hpp @@ -3,12 +3,9 @@ #include #include -#include -#include #include "ArrayBackedPosIntSet.hpp" #include "Defs.h" -#include "dictionary_utils.hpp" #include "FileWriter.hpp" #include "spdlog_with_specializations.hpp" #include "streaming_compression/passthrough/Compressor.hpp" @@ -63,18 +60,6 @@ class DictionaryWriter { */ void write_header_and_flush_to_disk(); - /** - * Opens dictionary, loads entries, and then sets it up for writing - * @param dictionary_path - * @param segment_index_path - * @param max_id - */ - void open_and_preload( - std::string const& dictionary_path, - std::string const& segment_index_path, - variable_dictionary_id_t max_id - ); - /** * Adds the given segment and IDs to the segment index * @param segment_id @@ -98,7 +83,7 @@ class DictionaryWriter { protected: // Types - typedef std::unordered_map value_to_id_t; + using value_to_id_t = std::unordered_map; // Variables bool m_is_open; @@ -191,85 +176,6 @@ void DictionaryWriter::write_header_and_flush_to_di m_dictionary_file_writer.flush(); } -template -void DictionaryWriter::open_and_preload( - std::string const& dictionary_path, - std::string const& segment_index_path, - variable_dictionary_id_t const max_id -) { - if (m_is_open) { - throw OperationFailed(ErrorCode_NotReady, __FILENAME__, __LINE__); - } - - m_max_id = max_id; - - FileReader dictionary_file_reader; - FileReader segment_index_file_reader; -#if USE_PASSTHROUGH_COMPRESSION - streaming_compression::passthrough::Decompressor dictionary_decompressor; - streaming_compression::passthrough::Decompressor segment_index_decompressor; -#elif USE_ZSTD_COMPRESSION - streaming_compression::zstd::Decompressor dictionary_decompressor; - streaming_compression::zstd::Decompressor segment_index_decompressor; -#else - static_assert(false, "Unsupported compression mode."); -#endif - constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB - open_dictionary_for_reading( - dictionary_path, - segment_index_path, - cDecompressorFileReadBufferCapacity, - dictionary_file_reader, - dictionary_decompressor, - segment_index_file_reader, - segment_index_decompressor - ); - - auto num_dictionary_entries = read_dictionary_header(dictionary_file_reader); - if (num_dictionary_entries > m_max_id) { - SPDLOG_ERROR("DictionaryWriter ran out of IDs."); - throw OperationFailed(ErrorCode_OutOfBounds, __FILENAME__, __LINE__); - } - // Loads entries from the given dictionary file - EntryType entry; - for (size_t i = 0; i < num_dictionary_entries; ++i) { - entry.clear(); - entry.read_from_file(dictionary_decompressor); - auto const& str_value = entry.get_value(); - if (m_value_to_id.count(str_value)) { - SPDLOG_ERROR("Entry's value already exists in dictionary"); - throw OperationFailed(ErrorCode_Corrupt, __FILENAME__, __LINE__); - } - - m_value_to_id[str_value] = entry.get_id(); - ; - m_data_size += entry.get_data_size(); - } - - m_next_id = num_dictionary_entries; - - segment_index_decompressor.close(); - segment_index_file_reader.close(); - dictionary_decompressor.close(); - dictionary_file_reader.close(); - - m_dictionary_file_writer.open( - dictionary_path, - FileWriter::OpenMode::CREATE_IF_NONEXISTENT_FOR_SEEKABLE_WRITING - ); - // Open compressor - m_dictionary_compressor.open(m_dictionary_file_writer); - - m_segment_index_file_writer.open( - segment_index_path, - FileWriter::OpenMode::CREATE_IF_NONEXISTENT_FOR_SEEKABLE_WRITING - ); - // Open compressor - m_segment_index_compressor.open(m_segment_index_file_writer); - - m_is_open = true; -} - template void DictionaryWriter::index_segment( segment_id_t segment_id, diff --git a/components/core/src/clp/EncodedVariableInterpreter.cpp b/components/core/src/clp/EncodedVariableInterpreter.cpp index ad7116bfe..8170f2ddc 100644 --- a/components/core/src/clp/EncodedVariableInterpreter.cpp +++ b/components/core/src/clp/EncodedVariableInterpreter.cpp @@ -234,7 +234,8 @@ void EncodedVariableInterpreter::encode_and_add_to_dictionary( size_t& raw_num_bytes ) { logtype_dict_entry.clear(); - logtype_dict_entry.reserve_constant_length(log_event.get_logtype().length()); + auto const& log_message = log_event.get_message(); + logtype_dict_entry.reserve_constant_length(log_message.get_logtype().length()); raw_num_bytes = 0; @@ -284,9 +285,9 @@ void EncodedVariableInterpreter::encode_and_add_to_dictionary( }; ffi::ir_stream::generic_decode_message( - log_event.get_logtype(), - log_event.get_encoded_vars(), - log_event.get_dict_vars(), + log_message.get_logtype(), + log_message.get_encoded_vars(), + log_message.get_dict_vars(), constant_handler, encoded_int_handler, encoded_float_handler, diff --git a/components/core/src/clp/FileDescriptor.cpp b/components/core/src/clp/FileDescriptor.cpp new file mode 100644 index 000000000..480859bd7 --- /dev/null +++ b/components/core/src/clp/FileDescriptor.cpp @@ -0,0 +1,70 @@ +#include "FileDescriptor.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "ErrorCode.hpp" +#include "type_utils.hpp" + +namespace clp { +FileDescriptor::FileDescriptor( + std::string_view path, + OpenMode open_mode, + CloseFailureCallback close_failure_callback +) + : m_open_mode{open_mode}, + m_close_failure_callback{close_failure_callback} { + // For newly created files, we enable writing for the owner and reading for everyone. + // Callers can change the created file's permissions as necessary. + constexpr auto cNewFilePermission{S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH}; + auto const flag{enum_to_underlying_type(open_mode)}; + if (0 != (flag & O_CREAT)) { + m_fd = open(path.data(), flag, cNewFilePermission); + } else { + m_fd = open(path.data(), flag); + } + if (-1 == m_fd) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "Failed to open file descriptor for path: " + std::string{path} + ); + } +} + +FileDescriptor::~FileDescriptor() { + if (-1 == m_fd) { + return; + } + if (0 != close(m_fd) && nullptr != m_close_failure_callback) { + m_close_failure_callback(errno); + } +} + +auto FileDescriptor::get_size() const -> size_t { + struct stat stat_result {}; + + if (ErrorCode_Success != stat(stat_result)) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "Failed to stat file using file descriptor." + ); + } + return static_cast(stat_result.st_size); +} + +auto FileDescriptor::stat(struct stat& stat_buffer) const -> ErrorCode { + if (0 != fstat(m_fd, &stat_buffer)) { + return ErrorCode_errno; + } + return ErrorCode_Success; +} +} // namespace clp diff --git a/components/core/src/clp/FileDescriptor.hpp b/components/core/src/clp/FileDescriptor.hpp new file mode 100644 index 000000000..6770f3aef --- /dev/null +++ b/components/core/src/clp/FileDescriptor.hpp @@ -0,0 +1,102 @@ +#ifndef CLP_FILEDESCRIPTOR_HPP +#define CLP_FILEDESCRIPTOR_HPP + +#include +#include + +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +/** + * Wrapper for a UNIX file descriptor. + */ +class FileDescriptor { +public: + // Types + /** + * `close` is called in the destructor to close the file descriptor. However, `close` may return + * an error indicated by `errno`. This type alias defines a callback to handle the `close` + * failure in the destructor. + * The signature of the callback: void close_failure_callback(int errno) + */ + using CloseFailureCallback = void (*)(int); + + class OperationFailed : public TraceableException { + public: + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string msg + ) + : TraceableException{error_code, filename, line_number}, + m_msg{std::move(msg)} {} + + [[nodiscard]] auto what() const noexcept -> char const* override { return m_msg.c_str(); } + + private: + std::string m_msg; + }; + + /** + * A C++ wrapper for Unix oflag that describes the open mode. + */ + // NOLINTNEXTLINE(performance-enum-size) + enum class OpenMode : int { + ReadOnly = O_RDONLY, + CreateForWrite = O_WRONLY | O_CREAT | O_TRUNC, + }; + + // Constructors + FileDescriptor( + std::string_view path, + OpenMode open_mode, + CloseFailureCallback close_failure_callback = nullptr + ); + + // Destructor + ~FileDescriptor(); + + // Disable copy/move constructors/assignment operators + FileDescriptor(FileDescriptor const&) = delete; + FileDescriptor(FileDescriptor&&) = delete; + auto operator=(FileDescriptor const&) -> FileDescriptor& = delete; + auto operator=(FileDescriptor&&) -> FileDescriptor& = delete; + + /** + * @return The raw fd. + */ + [[nodiscard]] auto get_raw_fd() const -> int { return m_fd; } + + /** + * @return The size of the file. + */ + [[nodiscard]] auto get_size() const -> size_t; + + /** + * @return The open mode. + */ + [[nodiscard]] auto get_open_mode() const -> OpenMode { return m_open_mode; } + + /** + * Obtains information about the open file associated with the underlying file descriptor. + * @param stat_buffer Returns the stat results. + * @return ErrorCode_Success on success. + * @return ErrorCode_errno on error. + */ + [[nodiscard]] auto stat(struct stat& stat_buffer) const -> ErrorCode; + +private: + int m_fd{-1}; + OpenMode m_open_mode; + CloseFailureCallback m_close_failure_callback{nullptr}; +}; +} // namespace clp + +#endif diff --git a/components/core/src/clp/FileDescriptorReader.cpp b/components/core/src/clp/FileDescriptorReader.cpp new file mode 100644 index 000000000..4beb926a9 --- /dev/null +++ b/components/core/src/clp/FileDescriptorReader.cpp @@ -0,0 +1,58 @@ +#include "FileDescriptorReader.hpp" + +#include +#include + +#include +#include +#include + +#include "ErrorCode.hpp" + +using std::span; + +namespace clp { +auto FileDescriptorReader::try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) + -> ErrorCode { + if (nullptr == buf) { + return ErrorCode_BadParam; + } + + num_bytes_read = 0; + span dst_view{buf, num_bytes_to_read}; + while (false == dst_view.empty()) { + auto const bytes_read = ::read(m_fd.get_raw_fd(), dst_view.data(), dst_view.size()); + if (0 == bytes_read) { + break; + } + if (bytes_read < 0) { + return ErrorCode_errno; + } + num_bytes_read += bytes_read; + dst_view = dst_view.subspan(bytes_read); + } + if (dst_view.size() == num_bytes_to_read) { + return ErrorCode_EndOfFile; + } + return ErrorCode_Success; +} + +auto FileDescriptorReader::try_seek_from_begin(size_t pos) -> ErrorCode { + if (auto const offset = lseek(m_fd.get_raw_fd(), static_cast(pos), SEEK_SET); + static_cast(-1) == offset) + { + return ErrorCode_errno; + } + + return ErrorCode_Success; +} + +auto FileDescriptorReader::try_get_pos(size_t& pos) -> ErrorCode { + auto const curr_offset = lseek(m_fd.get_raw_fd(), 0, SEEK_CUR); + if (static_cast(-1) == curr_offset) { + return ErrorCode_errno; + } + pos = static_cast(curr_offset); + return ErrorCode_Success; +} +} // namespace clp diff --git a/components/core/src/clp/FileDescriptorReader.hpp b/components/core/src/clp/FileDescriptorReader.hpp new file mode 100644 index 000000000..ca751471a --- /dev/null +++ b/components/core/src/clp/FileDescriptorReader.hpp @@ -0,0 +1,104 @@ +#ifndef CLP_FILEDESCRIPTORREADER_HPP +#define CLP_FILEDESCRIPTORREADER_HPP + +#include + +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "FileDescriptor.hpp" +#include "ReaderInterface.hpp" +#include "TraceableException.hpp" + +namespace clp { +/** + * Class for performing direct reads from an on-disk file using `clp::FileDescriptor` and C-style + * system call. Unlike `clp::FileReader`, which uses on `FILE` stream interface to buffer read data, + * this class does not buffer data internally. Instead, the user of this class is expected to + * buffer and read the data efficiently. + * + * Note: If you don't plan to handle the data buffering yourself, do not use this class. Use + * `clp::FileReader` instead. + */ +class FileDescriptorReader : public ReaderInterface { +public: + // Types + class OperationFailed : public TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : TraceableException(error_code, filename, line_number) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { + return "clp::FileDescriptorReader operation failed"; + } + }; + + // Constructors + explicit FileDescriptorReader(std::string path) + : m_path{std::move(path)}, + m_fd{m_path, FileDescriptor::OpenMode::ReadOnly} {} + + // Explicitly disable copy constructor and assignment operator + FileDescriptorReader(FileDescriptorReader const&) = delete; + auto operator=(FileDescriptorReader const&) -> FileDescriptorReader& = delete; + + // Explicitly disable move constructor and assignment operator + FileDescriptorReader(FileDescriptorReader&&) = delete; + auto operator=(FileDescriptorReader&&) -> FileDescriptorReader& = delete; + + // Destructor + ~FileDescriptorReader() override = default; + + // Methods implementing the ReaderInterface + /** + * Tries to read up to a given number of bytes from the file. + * @param buf + * @param num_bytes_to_read The number of bytes to try and read + * @param num_bytes_read The actual number of bytes read + * @return ErrorCode_BadParam if buf is invalid + * @return ErrorCode_errno on error + * @return ErrorCode_EndOfFile on EOF + * @return ErrorCode_Success on success + */ + [[nodiscard]] auto + try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) -> ErrorCode override; + + /** + * Tries to seek to the given position, relative to the beginning of the file. + * @param pos + * @return ErrorCode_errno on error + * @return ErrorCode_Success on success + */ + [[nodiscard]] auto try_seek_from_begin(size_t pos) -> ErrorCode override; + + /** + @param pos Returns the position of the read head in the buffer. + * @return ErrorCode_errno on error + * @return ErrorCode_Success on success + */ + [[nodiscard]] auto try_get_pos(size_t& pos) -> ErrorCode override; + + // Methods + [[nodiscard]] auto get_path() const -> std::string_view { return m_path; } + + /** + * Obtains information about the open file associated with the underlying file descriptor. + * @param stat_buffer Returns the stat results. + * @return Same as `FileDescriptor::fstat` + */ + [[nodiscard]] auto try_fstat(struct stat& stat_buffer) const -> ErrorCode { + return m_fd.stat(stat_buffer); + } + +private: + std::string m_path; + FileDescriptor m_fd; +}; +} // namespace clp + +#endif // CLP_FILEDESCRIPTORREADER_HPP diff --git a/components/core/src/clp/FileReader.cpp b/components/core/src/clp/FileReader.cpp index 06a986383..8a51b1827 100644 --- a/components/core/src/clp/FileReader.cpp +++ b/components/core/src/clp/FileReader.cpp @@ -12,15 +12,26 @@ using std::string; namespace clp { +FileReader::FileReader(string const& path) : m_file{fopen(path.c_str(), "rb")} { + if (nullptr == m_file) { + if (ENOENT == errno) { + throw OperationFailed(ErrorCode_FileNotFound, __FILE__, __LINE__); + } + throw OperationFailed(ErrorCode_errno, __FILE__, __LINE__); + } + m_path = path; +} + FileReader::~FileReader() { - close(); + if (nullptr != m_file) { + // NOTE: We don't check errors for fclose since it seems the only reason it could fail is + // if it was interrupted by a signal + fclose(m_file); + } free(m_getdelim_buf); } ErrorCode FileReader::try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) { - if (nullptr == m_file) { - return ErrorCode_NotInit; - } if (nullptr == buf) { return ErrorCode_BadParam; } @@ -40,10 +51,6 @@ ErrorCode FileReader::try_read(char* buf, size_t num_bytes_to_read, size_t& num_ } ErrorCode FileReader::try_seek_from_begin(size_t pos) { - if (nullptr == m_file) { - return ErrorCode_NotInit; - } - int retval = fseeko(m_file, pos, SEEK_SET); if (0 != retval) { return ErrorCode_errno; @@ -53,10 +60,6 @@ ErrorCode FileReader::try_seek_from_begin(size_t pos) { } ErrorCode FileReader::try_get_pos(size_t& pos) { - if (nullptr == m_file) { - return ErrorCode_NotInit; - } - pos = ftello(m_file); if ((off_t)-1 == pos) { return ErrorCode_errno; @@ -65,49 +68,14 @@ ErrorCode FileReader::try_get_pos(size_t& pos) { return ErrorCode_Success; } -ErrorCode FileReader::try_open(string const& path) { - // Cleanup in case caller forgot to call close before calling this function - close(); - - m_file = fopen(path.c_str(), "rb"); - if (nullptr == m_file) { - if (ENOENT == errno) { - return ErrorCode_FileNotFound; - } - return ErrorCode_errno; - } - m_path = path; - - return ErrorCode_Success; -} - -void FileReader::open(string const& path) { - ErrorCode error_code = try_open(path); - if (ErrorCode_Success != error_code) { - if (ErrorCode_FileNotFound == error_code) { - throw "File not found: " + boost::filesystem::weakly_canonical(path).string() + "\n"; - } else { - throw OperationFailed(error_code, __FILENAME__, __LINE__); - } - } -} - -void FileReader::close() { - if (m_file != nullptr) { - // NOTE: We don't check errors for fclose since it seems the only reason it could fail is if - // it was interrupted by a signal - fclose(m_file); - m_file = nullptr; - } -} - ErrorCode FileReader::try_read_to_delimiter(char delim, bool keep_delimiter, bool append, string& str) { - assert(nullptr != m_file); - if (false == append) { str.clear(); } + + // NOTE: If `m_getdelim_buf` is a null pointer or if `m_getdelim_buf_len` is insufficient in + // size, `getdelim` will malloc or realloc enough memory, respectively, to hold the characters. ssize_t num_bytes_read = getdelim(&m_getdelim_buf, &m_getdelim_buf_len, delim, m_file); if (num_bytes_read < 1) { if (ferror(m_file)) { @@ -125,10 +93,6 @@ FileReader::try_read_to_delimiter(char delim, bool keep_delimiter, bool append, } ErrorCode FileReader::try_fstat(struct stat& stat_buffer) { - if (nullptr == m_file) { - throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); - } - auto return_value = fstat(fileno(m_file), &stat_buffer); if (0 != return_value) { return ErrorCode_errno; diff --git a/components/core/src/clp/FileReader.hpp b/components/core/src/clp/FileReader.hpp index 56e376af6..269297cf3 100644 --- a/components/core/src/clp/FileReader.hpp +++ b/components/core/src/clp/FileReader.hpp @@ -25,7 +25,7 @@ class FileReader : public ReaderInterface { char const* what() const noexcept override { return "FileReader operation failed"; } }; - FileReader() : m_file(nullptr), m_getdelim_buf_len(0), m_getdelim_buf(nullptr) {} + FileReader(std::string const& path); ~FileReader(); @@ -33,7 +33,6 @@ class FileReader : public ReaderInterface { /** * Tries to get the current position of the read head in the file * @param pos Position of the read head in the file - * @return ErrorCode_NotInit if the file is not open * @return ErrorCode_errno on error * @return ErrorCode_Success on success */ @@ -41,7 +40,6 @@ class FileReader : public ReaderInterface { /** * Tries to seek from the beginning of the file to the given position * @param pos - * @return ErrorCode_NotInit if the file is not open * @return ErrorCode_errno on error * @return ErrorCode_Success on success */ @@ -52,7 +50,6 @@ class FileReader : public ReaderInterface { * @param buf * @param num_bytes_to_read The number of bytes to try and read * @param num_bytes_read The actual number of bytes read - * @return ErrorCode_NotInit if the file is not open * @return ErrorCode_BadParam if buf is invalid * @return ErrorCode_errno on error * @return ErrorCode_EndOfFile on EOF @@ -73,28 +70,6 @@ class FileReader : public ReaderInterface { ErrorCode try_read_to_delimiter(char delim, bool keep_delimiter, bool append, std::string& str) override; - // Methods - bool is_open() const { return m_file != nullptr; } - - /** - * Tries to open a file - * @param path - * @return ErrorCode_Success on success - * @return ErrorCode_FileNotFound if the file was not found - * @return ErrorCode_errno otherwise - */ - ErrorCode try_open(std::string const& path); - /** - * Opens a file - * @param path - * @throw FileReader::OperationFailed on failure - */ - void open(std::string const& path); - /** - * Closes the file if it's open - */ - void close(); - [[nodiscard]] std::string const& get_path() const { return m_path; } /** @@ -106,9 +81,9 @@ class FileReader : public ReaderInterface { ErrorCode try_fstat(struct stat& stat_buffer); private: - FILE* m_file; - size_t m_getdelim_buf_len; - char* m_getdelim_buf; + FILE* m_file{nullptr}; + size_t m_getdelim_buf_len{0}; + char* m_getdelim_buf{nullptr}; std::string m_path; }; } // namespace clp diff --git a/components/core/src/clp/Grep.hpp b/components/core/src/clp/Grep.hpp index ebd007bae..f520af212 100644 --- a/components/core/src/clp/Grep.hpp +++ b/components/core/src/clp/Grep.hpp @@ -22,7 +22,7 @@ class Grep { * @param decompressed_msg * @param custom_arg Custom argument for the output function */ - typedef void (*OutputFunc)( + using OutputFunc = void (*)( std::string const& orig_file_path, streaming_archive::reader::Message const& compressed_msg, std::string const& decompressed_msg, diff --git a/components/core/src/clp/MySQLDB.hpp b/components/core/src/clp/MySQLDB.hpp index d60e84bce..42635449b 100644 --- a/components/core/src/clp/MySQLDB.hpp +++ b/components/core/src/clp/MySQLDB.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "Defs.h" #include "ErrorCode.hpp" diff --git a/components/core/src/clp/MySQLParamBindings.hpp b/components/core/src/clp/MySQLParamBindings.hpp index 42a81e4eb..5a7c7d636 100644 --- a/components/core/src/clp/MySQLParamBindings.hpp +++ b/components/core/src/clp/MySQLParamBindings.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "ErrorCode.hpp" #include "TraceableException.hpp" diff --git a/components/core/src/clp/MySQLPreparedStatement.hpp b/components/core/src/clp/MySQLPreparedStatement.hpp index 1abf3f828..5e25be6f3 100644 --- a/components/core/src/clp/MySQLPreparedStatement.hpp +++ b/components/core/src/clp/MySQLPreparedStatement.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "ErrorCode.hpp" #include "MySQLParamBindings.hpp" diff --git a/components/core/src/clp/NetworkReader.cpp b/components/core/src/clp/NetworkReader.cpp index 1ab746a54..086b60681 100644 --- a/components/core/src/clp/NetworkReader.cpp +++ b/components/core/src/clp/NetworkReader.cpp @@ -6,13 +6,17 @@ #include #include #include +#include #include +#include +#include #include #include "CurlDownloadHandler.hpp" #include "CurlOperationFailed.hpp" #include "ErrorCode.hpp" +#include "Platform.hpp" namespace clp { /** @@ -110,33 +114,6 @@ curl_write_callback(char* ptr, size_t size, size_t nmemb, void* reader_ptr) -> s } } // namespace -bool NetworkReader::m_static_init_complete{false}; - -auto NetworkReader::init() -> ErrorCode { - if (m_static_init_complete) { - return ErrorCode_Success; - } - if (0 != curl_global_init(CURL_GLOBAL_ALL)) { - return ErrorCode_Failure; - } - m_static_init_complete = true; - return ErrorCode_Success; -} - -auto NetworkReader::deinit() -> void { -#if defined(__APPLE__) - // NOTE: On macOS, calling `curl_global_init` after `curl_global_cleanup` will fail with - // CURLE_SSL_CONNECT_ERROR. Thus, for now, we skip `deinit` on macOS. Related issues: - // - https://github.com/curl/curl/issues/12525 - // - https://github.com/curl/curl/issues/13805 - // TODO: Remove this conditional logic when the issues are resolved. - return; -#else - curl_global_cleanup(); - m_static_init_complete = false; -#endif -} - NetworkReader::NetworkReader( std::string_view src_url, size_t offset, @@ -144,7 +121,8 @@ NetworkReader::NetworkReader( std::chrono::seconds overall_timeout, std::chrono::seconds connection_timeout, size_t buffer_pool_size, - size_t buffer_size + size_t buffer_size, + std::optional> http_header_kv_pairs ) : m_src_url{src_url}, m_offset{offset}, @@ -154,13 +132,14 @@ NetworkReader::NetworkReader( m_buffer_pool_size{std::max(cMinBufferPoolSize, buffer_pool_size)}, m_buffer_size{std::max(cMinBufferSize, buffer_size)} { for (size_t i = 0; i < m_buffer_pool_size; ++i) { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - m_buffer_pool.emplace_back(std::make_unique(m_buffer_size)); - } - if (false == m_static_init_complete) { - throw OperationFailed(ErrorCode_NotReady, __FILE__, __LINE__); + m_buffer_pool.emplace_back(m_buffer_size); } - m_downloader_thread = std::make_unique(*this, offset, disable_caching); + m_downloader_thread = std::make_unique( + *this, + offset, + disable_caching, + std::move(http_header_kv_pairs) + ); m_downloader_thread->start(); } @@ -188,6 +167,13 @@ auto NetworkReader::try_get_pos(size_t& pos) -> ErrorCode { // offset specified in the HTTP header. return ErrorCode_Failure; } + if constexpr (Platform::MacOs == cCurrentPlatform) { + // On macOS, HTTP response code 416 is not handled as `CURL_HTTP_RETURNED_ERROR` in + // some `libcurl` versions. + if (CURLE_RECV_ERROR == curl_return_code.value()) { + return ErrorCode_Failure; + } + } } if (false == at_least_one_byte_downloaded()) { @@ -230,6 +216,7 @@ auto NetworkReader::buffer_downloaded_data(NetworkReader::BufferView data) -> si auto NetworkReader::DownloaderThread::thread_method() -> void { try { CurlDownloadHandler curl_handler{ + m_reader.m_curl_error_msg_buf, curl_progress_callback, curl_write_callback, static_cast(&m_reader), @@ -237,7 +224,8 @@ auto NetworkReader::DownloaderThread::thread_method() -> void { m_offset, m_disable_caching, m_reader.m_connection_timeout, - m_reader.m_overall_timeout + m_reader.m_overall_timeout, + m_http_header_kv_pairs }; auto const ret_code{curl_handler.perform()}; // Enqueue the last filled buffer, if any @@ -269,7 +257,10 @@ auto NetworkReader::acquire_empty_buffer() -> void { return; } } - m_curr_downloader_buf.emplace(m_buffer_pool.at(m_curr_downloader_buf_idx).get(), m_buffer_size); + m_curr_downloader_buf.emplace( + m_buffer_pool.at(m_curr_downloader_buf_idx).data(), + m_buffer_size + ); } auto NetworkReader::release_empty_buffer() -> void { @@ -285,7 +276,7 @@ auto NetworkReader::enqueue_filled_buffer() -> void { } std::unique_lock const buffer_resource_lock{m_buffer_resource_mutex}; m_filled_buffer_queue.emplace( - m_buffer_pool.at(m_curr_downloader_buf_idx).get(), + m_buffer_pool.at(m_curr_downloader_buf_idx).data(), m_buffer_size - m_curr_downloader_buf.value().size() ); diff --git a/components/core/src/clp/NetworkReader.hpp b/components/core/src/clp/NetworkReader.hpp index 5a593029f..08be975ea 100644 --- a/components/core/src/clp/NetworkReader.hpp +++ b/components/core/src/clp/NetworkReader.hpp @@ -13,11 +13,15 @@ #include #include #include +#include +#include #include #include +#include "Array.hpp" #include "CurlDownloadHandler.hpp" +#include "CurlGlobalInstance.hpp" #include "ErrorCode.hpp" #include "ReaderInterface.hpp" #include "Thread.hpp" @@ -69,24 +73,20 @@ class NetworkReader : public ReaderInterface { static constexpr size_t cMinBufferPoolSize{2}; static constexpr size_t cMinBufferSize{512}; - /** - * Initializes static resources for this class. This must be called before using the class. - * @return ErrorCode_Success on success. - * @return ErrorCode_Failure if libcurl initialization failed. - */ - [[nodiscard]] static auto init() -> ErrorCode; - - /** - * De-initializes any static resources. - */ - static auto deinit() -> void; - /** * Constructs a reader to stream data from the given URL, starting at the given offset. - * TODO: the current implementation doesn't handle the case when the given offset is out of - * range. The file_pos will be set to an invalid state if this happens, which can be - * problematic if the other part of the program depends on this position. It can be fixed by - * capturing the error code 416 in the response header. + * NOTE: This class depends on `libcurl`, so an instance of `clp::CurlGlobalInstance` must + * remain alive for the entire lifespan of any instance of this class. + * + * This class maintains an instance of `CurlGlobalInstance` in case the user forgets to + * instantiate one, but it is better for performance if the user instantiates one. For instance, + * if the user doesn't instantiate a `CurlGlobalInstance` and only ever creates one + * `NetworkReader` at a time, then every construction and destruction of `NetworkReader` would + * cause `libcurl` to init and deinit. In contrast, if the user instantiates a + * `CurlGlobalInstance` before instantiating any `NetworkReader`s, then the `CurlGlobalInstance` + * maintained by this class will simply be a reference to the existing one rather than + * initializing and deinitializing `libcurl`. + * * @param src_url * @param offset Index of the byte at which to start the download * @param disable_caching Whether to disable the caching. @@ -96,6 +96,8 @@ class NetworkReader : public ReaderInterface { * Doc: https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT.html * @param buffer_pool_size The required number of buffers in the buffer pool. * @param buffer_size The size of each buffer in the buffer pool. + * @param http_header_kv_pairs Key-value pairs representing HTTP headers to pass to the server + * in the download request. Doc: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html */ explicit NetworkReader( std::string_view src_url, @@ -105,11 +107,13 @@ class NetworkReader : public ReaderInterface { std::chrono::seconds connection_timeout = CurlDownloadHandler::cDefaultConnectionTimeout, size_t buffer_pool_size = cDefaultBufferPoolSize, - size_t buffer_size = cDefaultBufferSize + size_t buffer_size = cDefaultBufferSize, + std::optional> http_header_kv_pairs + = std::nullopt ); // Destructor - virtual ~NetworkReader(); + ~NetworkReader() override; // Copy/Move Constructors // These are disabled since this class' synchronization primitives are non-copyable and @@ -219,6 +223,19 @@ class NetworkReader : public ReaderInterface { return m_curl_ret_code.load(); } + /** + * @return The error message set by the underlying CURL handler. + * @return std::nullopt if the download is still in-progress or no error has occured. + */ + [[nodiscard]] auto get_curl_error_msg() const -> std::optional { + if (auto const ret_code{get_curl_ret_code()}; + false == ret_code.has_value() || CURLE_OK == ret_code.value()) + { + return std::nullopt; + } + return std::string_view{m_curl_error_msg_buf->data()}; + } + private: /** * This class implements clp::Thread to download data using CURL. @@ -231,11 +248,19 @@ class NetworkReader : public ReaderInterface { * @param reader * @param offset Index of the byte at which to start the download. * @param disable_caching Whether to disable caching. + * @param http_header_kv_pairs Key-value pairs representing HTTP headers to pass to the + * server in the download request. Doc: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html */ - DownloaderThread(NetworkReader& reader, size_t offset, bool disable_caching) + DownloaderThread( + NetworkReader& reader, + size_t offset, + bool disable_caching, + std::optional> http_header_kv_pairs + ) : m_reader{reader}, m_offset{offset}, - m_disable_caching{disable_caching} {} + m_disable_caching{disable_caching}, + m_http_header_kv_pairs{std::move(http_header_kv_pairs)} {} private: // Methods implementing `clp::Thread` @@ -244,10 +269,9 @@ class NetworkReader : public ReaderInterface { NetworkReader& m_reader; size_t m_offset{0}; bool m_disable_caching{false}; + std::optional> m_http_header_kv_pairs; }; - static bool m_static_init_complete; - /** * Submits a request to abort the ongoing curl download session. */ @@ -308,6 +332,8 @@ class NetworkReader : public ReaderInterface { return m_at_least_one_byte_downloaded.load(); } + CurlGlobalInstance m_curl_global_instance; + std::string m_src_url; size_t m_offset{0}; @@ -320,8 +346,7 @@ class NetworkReader : public ReaderInterface { size_t m_buffer_size{cDefaultBufferSize}; size_t m_curr_downloader_buf_idx{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - std::vector> m_buffer_pool; + std::vector> m_buffer_pool; std::queue m_filled_buffer_queue; std::optional m_curr_downloader_buf; std::optional m_curr_reader_buf; @@ -337,6 +362,10 @@ class NetworkReader : public ReaderInterface { // These two members should only be set from `set_download_completion_status` std::atomic m_state{State::InProgress}; std::atomic> m_curl_ret_code; + + std::shared_ptr m_curl_error_msg_buf{ + std::make_shared() + }; }; } // namespace clp diff --git a/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp b/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp new file mode 100644 index 000000000..167b675a5 --- /dev/null +++ b/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp @@ -0,0 +1,41 @@ +#include "ReadOnlyMemoryMappedFile.hpp" + +#include + +#include +#include + +#include "ErrorCode.hpp" +#include "FileDescriptor.hpp" + +namespace clp { +ReadOnlyMemoryMappedFile::ReadOnlyMemoryMappedFile(std::string_view path) { + FileDescriptor const fd{path, FileDescriptor::OpenMode::ReadOnly}; + auto const file_size{fd.get_size()}; + if (0 == file_size) { + // `mmap` doesn't allow mapping an empty file, so we don't need to call it. + return; + } + auto* mmap_ptr{mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd.get_raw_fd(), 0)}; + if (MAP_FAILED == mmap_ptr) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "`mmap` failed to map path: " + std::string{path} + ); + } + m_data = mmap_ptr; + m_buf_size = file_size; +} + +ReadOnlyMemoryMappedFile::~ReadOnlyMemoryMappedFile() { + if (0 == m_buf_size) { + // We don't call `mmap` for empty files, so we don't need to call `munmap`. + return; + } + // We skip error checking since the only likely reason for `munmap` to fail is if we give it + // invalid arguments. + munmap(m_data, m_buf_size); +} +} // namespace clp diff --git a/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp b/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp new file mode 100644 index 000000000..5749d3bd2 --- /dev/null +++ b/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp @@ -0,0 +1,66 @@ +#ifndef CLP_READONLYMEMORYMAPPEDFILE_HPP +#define CLP_READONLYMEMORYMAPPEDFILE_HPP + +#include +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +/** + * A class for mapping a read-only file into memory. It maintains the memory buffer created by the + * underlying `mmap` system call and provides methods to get a view of the memory buffer. + */ +class ReadOnlyMemoryMappedFile { +public: + // Types + class OperationFailed : public TraceableException { + public: + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string msg + ) + : TraceableException{error_code, filename, line_number}, + m_msg{std::move(msg)} {} + + [[nodiscard]] auto what() const noexcept -> char const* override { return m_msg.c_str(); } + + private: + std::string m_msg; + }; + + // Constructors + /** + * @param path The path of the file to map. + */ + explicit ReadOnlyMemoryMappedFile(std::string_view path); + + // Destructor + ~ReadOnlyMemoryMappedFile(); + + // Disable copy/move constructors/assignment operators + ReadOnlyMemoryMappedFile(ReadOnlyMemoryMappedFile const&) = delete; + ReadOnlyMemoryMappedFile(ReadOnlyMemoryMappedFile&&) = delete; + auto operator=(ReadOnlyMemoryMappedFile const&) -> ReadOnlyMemoryMappedFile& = delete; + auto operator=(ReadOnlyMemoryMappedFile&&) -> ReadOnlyMemoryMappedFile& = delete; + + /** + * @return A view of the mapped file in memory. + */ + [[nodiscard]] auto get_view() const -> std::span { + return std::span{static_cast(m_data), m_buf_size}; + } + +private: + void* m_data{nullptr}; + size_t m_buf_size{0}; +}; +} // namespace clp + +#endif diff --git a/components/core/src/clp/ReaderInterface.hpp b/components/core/src/clp/ReaderInterface.hpp index 39f914c2d..0a33598ce 100644 --- a/components/core/src/clp/ReaderInterface.hpp +++ b/components/core/src/clp/ReaderInterface.hpp @@ -22,6 +22,9 @@ class ReaderInterface { char const* what() const noexcept override { return "ReaderInterface operation failed"; } }; + // Destructor + virtual ~ReaderInterface() = default; + // Methods virtual ErrorCode try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) = 0; virtual ErrorCode try_seek_from_begin(size_t pos) = 0; diff --git a/components/core/src/clp/TraceableException.hpp b/components/core/src/clp/TraceableException.hpp index cd8e33f4b..f60273f93 100644 --- a/components/core/src/clp/TraceableException.hpp +++ b/components/core/src/clp/TraceableException.hpp @@ -39,6 +39,7 @@ class TraceableException : public std::exception { // Macros // Define a version of __FILE__ that's relative to the source directory #ifdef SOURCE_PATH_SIZE + // NOLINTNEXTLINE #define __FILENAME__ ((__FILE__) + SOURCE_PATH_SIZE) #else // We don't know the source path size, so just default to __FILE__ diff --git a/components/core/src/clp/TransactionManager.hpp b/components/core/src/clp/TransactionManager.hpp new file mode 100644 index 000000000..fe70f5547 --- /dev/null +++ b/components/core/src/clp/TransactionManager.hpp @@ -0,0 +1,51 @@ +#ifndef CLP_TRANSACTIONMANAGER_HPP +#define CLP_TRANSACTIONMANAGER_HPP + +#include + +namespace clp { +/** + * A class that on destruction, performs different actions depending on whether a transaction + * succeeds or fails. The default state assumes the transaction fails. + * @tparam SuccessHandler A cleanup lambda to call on success. + * @tparam FailureHandler A cleanup lambda to call on failure. + */ +template +requires(std::is_nothrow_invocable_v && std::is_nothrow_invocable_v) +class TransactionManager { +public: + // Constructor + TransactionManager(SuccessHandler success_handler, FailureHandler failure_handler) + : m_success_handler{success_handler}, + m_failure_handler{failure_handler} {} + + // Delete copy/move constructor and assignment + TransactionManager(TransactionManager const&) = delete; + TransactionManager(TransactionManager&&) = delete; + auto operator=(TransactionManager const&) -> TransactionManager& = delete; + auto operator=(TransactionManager&&) -> TransactionManager& = delete; + + // Destructor + ~TransactionManager() { + if (m_success) { + m_success_handler(); + } else { + m_failure_handler(); + } + } + + // Methods + /** + * Marks the transaction as successful. + */ + auto mark_success() -> void { m_success = true; } + +private: + // Variables + SuccessHandler m_success_handler; + FailureHandler m_failure_handler; + bool m_success{false}; +}; +} // namespace clp + +#endif // CLP_TRANSACTIONMANAGER_HPP diff --git a/components/core/src/clp/Utils.cpp b/components/core/src/clp/Utils.cpp index 1a45c5bf9..f487a3880 100644 --- a/components/core/src/clp/Utils.cpp +++ b/components/core/src/clp/Utils.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -17,7 +18,9 @@ #include "spdlog_with_specializations.hpp" using std::list; +using std::make_unique; using std::string; +using std::unique_ptr; using std::vector; namespace clp { @@ -137,16 +140,18 @@ string get_unambiguous_path(string const& path) { } ErrorCode read_list_of_paths(string const& list_path, vector& paths) { - FileReader file_reader; - ErrorCode error_code = file_reader.try_open(list_path); - if (ErrorCode_Success != error_code) { - return error_code; + unique_ptr file_reader; + try { + file_reader = make_unique(list_path); + } catch (FileReader::OperationFailed const& err) { + return err.get_error_code(); } // Read file string line; + ErrorCode error_code{ErrorCode_Success}; while (true) { - error_code = file_reader.try_read_to_delimiter('\n', false, false, line); + error_code = file_reader->try_read_to_delimiter('\n', false, false, line); if (ErrorCode_Success != error_code) { break; } @@ -160,8 +165,6 @@ ErrorCode read_list_of_paths(string const& list_path, vector& paths) { return error_code; } - file_reader.close(); - return ErrorCode_Success; } @@ -262,38 +265,29 @@ void load_lexer_from_file( } if (contains_delimiter) { - FileReader schema_reader; - ErrorCode error_code = schema_reader.try_open(schema_ast->m_file_path); - if (ErrorCode_Success != error_code) { - throw std::runtime_error( - schema_file_path + ":" + std::to_string(rule->m_line_num + 1) + ": error: '" - + rule->m_name + "' has regex pattern which contains delimiter '" - + char(delimiter_name) + "'.\n" - ); - } else { - // more detailed debugging based on looking at the file - string line; - for (uint32_t i = 0; i <= rule->m_line_num; i++) { - schema_reader.read_to_delimiter('\n', false, false, line); - } - int colon_pos = 0; - for (char i : line) { - colon_pos++; - if (i == ':') { - break; - } + FileReader schema_reader{schema_ast->m_file_path}; + // more detailed debugging based on looking at the file + string line; + for (uint32_t i = 0; i <= rule->m_line_num; i++) { + schema_reader.read_to_delimiter('\n', false, false, line); + } + int colon_pos = 0; + for (char i : line) { + colon_pos++; + if (i == ':') { + break; } - string indent(10, ' '); - string spaces(colon_pos, ' '); - string arrows(line.size() - colon_pos, '^'); - - throw std::runtime_error( - schema_file_path + ":" + std::to_string(rule->m_line_num + 1) + ": error: '" - + rule->m_name + "' has regex pattern which contains delimiter '" - + char(delimiter_name) + "'.\n" + indent + line + "\n" + indent + spaces - + arrows + "\n" - ); } + string indent(10, ' '); + string spaces(colon_pos, ' '); + string arrows(line.size() - colon_pos, '^'); + + throw std::runtime_error( + schema_file_path + ":" + std::to_string(rule->m_line_num + 1) + ": error: '" + + rule->m_name + "' has regex pattern which contains delimiter '" + + char(delimiter_name) + "'.\n" + indent + line + "\n" + indent + spaces + + arrows + "\n" + ); } lexer.add_rule(lexer.m_symbol_id[rule->m_name], std::move(rule->m_regex_ptr)); } diff --git a/components/core/src/clp/aws/AwsAuthenticationSigner.cpp b/components/core/src/clp/aws/AwsAuthenticationSigner.cpp new file mode 100644 index 000000000..ae4da33a2 --- /dev/null +++ b/components/core/src/clp/aws/AwsAuthenticationSigner.cpp @@ -0,0 +1,351 @@ +#include "AwsAuthenticationSigner.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../ErrorCode.hpp" +#include "../hash_utils.hpp" +#include "../type_utils.hpp" +#include "constants.hpp" + +using clp::string_utils::is_alphabet; +using clp::string_utils::is_decimal_digit; +using std::string; +using std::string_view; +using std::vector; + +namespace clp::aws { +namespace { +/** + * @param c + * @return Whether `c` is an unreserved character AWS Signature Version 4 protocol specifies. + */ +[[nodiscard]] auto is_unreserved_characters(char c) -> bool; + +/** + * Gets the formatted timestamp string specified by AWS Signature Version 4 format. + * @param timestamp + * @return The formatted timestamp string. + */ +[[nodiscard]] auto get_formatted_timestamp_string( + std::chrono::system_clock::time_point const& timestamp +) -> string; + +/** + * Gets the formatted date string specified by AWS Signature Version 4 format. + * @param timestamp + * @return The formatted date string. + */ +[[nodiscard]] auto get_formatted_date_string(std::chrono::system_clock::time_point const& timestamp +) -> string; + +/** + * Gets the string to sign required by AWS Signature Version 4 protocol. + * @param scope + * @param timestamp + * @param canonical_request + * @param string_to_sign Outputs the string to sign. + * @return ErrorCode_Success on success. + * @return Same as `get_sha256_hash` on failure. + */ +[[nodiscard]] auto get_string_to_sign( + string_view scope, + string_view timestamp, + string_view canonical_request, + string& string_to_sign +) -> ErrorCode; + +/** + * Encodes the Canonical URI as specified by AWS Signature Version 4's UriEncode. + * This method encodes the forward slash character, '/', everywhere except in the object key. + * @param uri + * @param is_object_key + * @return The encoded URI. + */ +[[nodiscard]] auto encode_uri(string_view uri, bool is_object_key) -> string; + +/** + * @param date + * @param region + * @return The formatted scope required by ASW Signature Version 4 protocol. + */ +[[nodiscard]] auto get_scope(string_view date, string_view region) -> string; + +/** + * @param url + * @param query_string + * @return Formatted canonical request string. + */ +[[nodiscard]] auto get_canonical_request(S3Url const& url, string_view query_string) -> string; + +auto is_unreserved_characters(char c) -> bool { + return is_alphabet(c) || is_decimal_digit(c) || c == '-' || c == '_' || c == '.' || c == '~'; +} + +auto get_formatted_timestamp_string(std::chrono::system_clock::time_point const& timestamp +) -> string { + return fmt::format("{:%Y%m%dT%H%M%SZ}", timestamp); +} + +auto get_formatted_date_string(std::chrono::system_clock::time_point const& timestamp) -> string { + return fmt::format("{:%Y%m%d}", timestamp); +} + +auto get_string_to_sign( + string_view scope, + string_view timestamp, + string_view canonical_request, + string& string_to_sign +) -> ErrorCode { + vector signed_canonical_request; + if (auto const error_code = get_sha256_hash( + {size_checked_pointer_cast(canonical_request.data()), + canonical_request.size()}, + signed_canonical_request + ); + ErrorCode_Success != error_code) + { + return error_code; + } + auto const signed_canonical_request_str = convert_to_hex_string( + {signed_canonical_request.data(), signed_canonical_request.size()} + ); + string_to_sign = fmt::format( + "{}\n{}\n{}\n{}", + cAws4HmacSha256, + timestamp, + scope, + signed_canonical_request_str + ); + return ErrorCode_Success; +} + +auto encode_uri(string_view uri, bool is_object_key) -> string { + string encoded_uri; + + for (auto const c : uri) { + if (is_unreserved_characters(c) || ('/' == c) && is_object_key) { + encoded_uri += c; + } else { + encoded_uri += fmt::format("%{:02X}", c); + } + } + + return encoded_uri; +} + +auto get_scope(string_view date, string_view region) -> string { + return fmt::format("{}/{}/{}/{}", date, region, cS3Service, cAws4Request); +} + +auto get_canonical_request(S3Url const& url, string_view query_string) -> string { + auto const uri_to_encode = fmt::format("/{}", url.get_key()); + return fmt::format( + "{}\n{}\n{}\n{}:{}\n\n{}\n{}", + AwsAuthenticationSigner::cHttpGetMethod, + encode_uri(uri_to_encode, true), + query_string, + cDefaultSignedHeaders, + url.get_host(), + cDefaultSignedHeaders, + cUnsignedPayload + ); +} +} // namespace + +S3Url::S3Url(string const& url) { + // Virtual-hosted-style HTTP URL format: https://[bucket].s3.[region].[endpoint]/[key] + boost::regex const host_style_url_regex{ + R"(https://(?[a-z0-9.-]+)\.s3(\.(?[a-z0-9-]+))?)" + R"(\.(?[a-z0-9.-]+)/(?[^?]+).*)" + }; + // Path-style HTTP URL format: https://s3.[region].[endpoint]/[bucket]/[key] + boost::regex const path_style_url_regex{ + R"(https://s3(\.(?[a-z0-9-]+))?)" + R"(\.(?[a-z0-9.-]+)/(?[a-z0-9.-]+)/(?[^?]+).*)" + }; + + if (boost::smatch match; boost::regex_match(url, match, host_style_url_regex) + || boost::regex_match(url, match, path_style_url_regex)) + { + m_region = match["region"]; + m_bucket = match["bucket"]; + m_key = match["key"]; + m_end_point = match["endpoint"]; + } else { + throw OperationFailed( + ErrorCode_BadParam, + __FILENAME__, + __LINE__, + "Invalid S3 HTTP URL format: " + url + ); + } + + if (cAwsEndpoint != m_end_point) { + throw OperationFailed( + ErrorCode_BadParam, + __FILENAME__, + __LINE__, + "Invalid S3 endpoint: " + m_end_point + ); + } + + if (m_region.empty()) { + m_region = cDefaultRegion; + } + m_host = fmt::format("{}.s3.{}.{}", m_bucket, m_region, m_end_point); +} + +auto AwsAuthenticationSigner::generate_presigned_url( + S3Url const& s3_url, + string& presigned_url +) const -> ErrorCode { + auto const s3_region = s3_url.get_region(); + + auto const now = std::chrono::system_clock::now(); + auto const timestamp = get_formatted_timestamp_string(now); + auto const date = get_formatted_date_string(now); + + auto const scope = get_scope(date, s3_region); + auto const canonical_query_string = get_canonical_query_string(scope, timestamp); + + auto const canonical_request = get_canonical_request(s3_url, canonical_query_string); + + string string_to_sign; + if (auto const error_code + = get_string_to_sign(scope, timestamp, canonical_request, string_to_sign); + ErrorCode_Success != error_code) + { + return error_code; + } + + vector signature; + if (auto const error_code = get_signature(s3_region, date, string_to_sign, signature); + ErrorCode_Success != error_code) + { + return error_code; + } + auto const signature_str = convert_to_hex_string({signature.data(), signature.size()}); + + presigned_url = fmt::format( + "https://{}/{}?{}&{}={}", + s3_url.get_host(), + s3_url.get_key(), + canonical_query_string, + cXAmzSignature, + signature_str + ); + return ErrorCode_Success; +} + +auto AwsAuthenticationSigner::get_canonical_query_string( + string_view scope, + string_view timestamp +) const -> string { + auto const uri = fmt::format("{}/{}", m_access_key_id, scope); + return fmt::format( + "{}={}&{}={}&{}={}&{}={}&{}={}", + cXAmzAlgorithm, + cAws4HmacSha256, + cXAmzCredential, + encode_uri(uri, false), + cXAmzDate, + timestamp, + cXAmzExpires, + cDefaultExpireTime.count(), + cXAmzSignedHeaders, + cDefaultSignedHeaders + ); +} + +auto AwsAuthenticationSigner::get_signing_key( + string_view region, + string_view date, + vector& signing_key +) const -> ErrorCode { + auto const key = fmt::format("{}{}", cAws4, m_secret_access_key); + + vector date_key; + if (auto const error_code = get_hmac_sha256_hash( + {size_checked_pointer_cast(date.data()), date.size()}, + {size_checked_pointer_cast(key.data()), key.size()}, + date_key + ); + error_code != ErrorCode_Success) + { + return error_code; + } + + vector date_region_key; + if (auto const error_code = get_hmac_sha256_hash( + {size_checked_pointer_cast(region.data()), region.size()}, + {size_checked_pointer_cast(date_key.data()), date_key.size()}, + date_region_key + ); + error_code != ErrorCode_Success) + { + return error_code; + } + + vector date_region_service_key; + if (auto const error_code = get_hmac_sha256_hash( + {size_checked_pointer_cast(cS3Service.data()), + cS3Service.size()}, + {size_checked_pointer_cast(date_region_key.data()), + date_region_key.size()}, + date_region_service_key + ); + error_code != ErrorCode_Success) + { + return error_code; + } + + if (auto const error_code = get_hmac_sha256_hash( + {size_checked_pointer_cast(cAws4Request.data()), + cAws4Request.size()}, + {size_checked_pointer_cast(date_region_service_key.data()), + date_region_service_key.size()}, + signing_key + ); + error_code != ErrorCode_Success) + { + return error_code; + } + + return ErrorCode_Success; +} + +auto AwsAuthenticationSigner::get_signature( + string_view region, + string_view date, + string_view string_to_sign, + vector& signature +) const -> ErrorCode { + vector signing_key; + if (auto const error_code = get_signing_key(region, date, signing_key); + ErrorCode_Success != error_code) + { + return error_code; + } + + if (auto const error_code = get_hmac_sha256_hash( + {size_checked_pointer_cast(string_to_sign.data()), + string_to_sign.size()}, + {size_checked_pointer_cast(signing_key.data()), + signing_key.size()}, + signature + ); + ErrorCode_Success != error_code) + { + return error_code; + } + return ErrorCode_Success; +} +} // namespace clp::aws diff --git a/components/core/src/clp/aws/AwsAuthenticationSigner.hpp b/components/core/src/clp/aws/AwsAuthenticationSigner.hpp new file mode 100644 index 000000000..a0a82eb2a --- /dev/null +++ b/components/core/src/clp/aws/AwsAuthenticationSigner.hpp @@ -0,0 +1,136 @@ +#ifndef CLP_AWS_AWSAUTHENTICATIONSIGNER_HPP +#define CLP_AWS_AWSAUTHENTICATIONSIGNER_HPP + +#include +#include +#include +#include +#include + +#include "../ErrorCode.hpp" +#include "../TraceableException.hpp" + +namespace clp::aws { +/** + * Class for a parsed S3 URL. + */ +class S3Url { +public: + // Types + class OperationFailed : public TraceableException { + public: + // Constructors + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string message = "S3Url operation failed" + ) + : TraceableException{error_code, filename, line_number}, + m_message{std::move(message)} {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + // Constructor + explicit S3Url(std::string const& url); + + // Methods + [[nodiscard]] auto get_region() const -> std::string_view { return m_region; } + + [[nodiscard]] auto get_bucket() const -> std::string_view { return m_bucket; } + + [[nodiscard]] auto get_key() const -> std::string_view { return m_key; } + + [[nodiscard]] auto get_host() const -> std::string_view { return m_host; } + +private: + std::string m_region; + std::string m_end_point; + std::string m_bucket; + std::string m_key; + std::string m_host; +}; + +/** + * Class for signing AWS requests based on AWS Signature Version 4. + */ +class AwsAuthenticationSigner { +public: + // Constants + // Default expire time of presigned URL in seconds + static constexpr std::chrono::seconds cDefaultExpireTime{86'400}; + static constexpr std::string_view cHttpGetMethod{"GET"}; + + // Constructors + AwsAuthenticationSigner(std::string access_key_id, std::string secret_access_key) + : m_access_key_id{std::move(access_key_id)}, + m_secret_access_key{std::move(secret_access_key)} {} + + // Methods + /** + * Generates a presigned S3 URL using AWS Signature Version 4 protocol. + * NOTE: the current implementation only supports generating URLs for HTTP GET operations. + * @param s3_url + * @param presigned_url Returns the generated presigned URL. + * @return ErrorCode_Success on success. + * @return Same as `get_sha256_hash` and `AwsAuthenticationSigner::get_signature` on failure. + */ + [[nodiscard]] auto + generate_presigned_url(S3Url const& s3_url, std::string& presigned_url) const -> ErrorCode; + +private: + /** + * Generates the canonical query string. + * @param scope + * @param timestamp + * @return The canonical query string. + */ + [[nodiscard]] auto get_canonical_query_string( + std::string_view scope, + std::string_view timestamp + ) const -> std::string; + + /** + * Gets the signature signing key for the request. + * @param region + * @param date + * @param signing_key Returns the signing key. + * @return ErrorCode_Success on success. + * @return Same as `get_hmac_sha256_hash` on Failure. + */ + [[nodiscard]] auto get_signing_key( + std::string_view region, + std::string_view date, + std::vector& signing_key + ) const -> ErrorCode; + + /** + * Signs the `string_to_sign` with a generated signing key. + * @param region + * @param date + * @param string_to_sign `StringToSign` required by AWS Signature Version 4 protocol. + * @param signature Returns the signature. + * @return ErrorCode_Success on success. + * @return Same as `get_hmac_sha256_hash` on Failure. + */ + [[nodiscard]] auto get_signature( + std::string_view region, + std::string_view date, + std::string_view string_to_sign, + std::vector& signature + ) const -> ErrorCode; + + // Variables + std::string m_access_key_id; + std::string m_secret_access_key; +}; +} // namespace clp::aws + +#endif // CLP_AWS_AWSAUTHENTICATIONSIGNER_HPP diff --git a/components/core/src/clp/aws/constants.hpp b/components/core/src/clp/aws/constants.hpp new file mode 100644 index 000000000..caebe92a1 --- /dev/null +++ b/components/core/src/clp/aws/constants.hpp @@ -0,0 +1,28 @@ +#ifndef CLP_AWS_CONSTANTS_HPP +#define CLP_AWS_CONSTANTS_HPP + +#include + +namespace clp::aws { +// Endpoint +constexpr std::string_view cAwsEndpoint{"amazonaws.com"}; + +// Query String Parameter Names +constexpr std::string_view cXAmzAlgorithm{"X-Amz-Algorithm"}; +constexpr std::string_view cXAmzCredential{"X-Amz-Credential"}; +constexpr std::string_view cXAmzDate{"X-Amz-Date"}; +constexpr std::string_view cXAmzExpires{"X-Amz-Expires"}; +constexpr std::string_view cXAmzSignature{"X-Amz-Signature"}; +constexpr std::string_view cXAmzSignedHeaders{"X-Amz-SignedHeaders"}; + +// Other Constants +constexpr std::string_view cAws4{"AWS4"}; +constexpr std::string_view cAws4Request{"aws4_request"}; +constexpr std::string_view cAws4HmacSha256{"AWS4-HMAC-SHA256"}; +constexpr std::string_view cDefaultSignedHeaders{"host"}; +constexpr std::string_view cDefaultRegion{"us-east-1"}; +constexpr std::string_view cS3Service{"s3"}; +constexpr std::string_view cUnsignedPayload{"UNSIGNED-PAYLOAD"}; +} // namespace clp::aws + +#endif // CLP_AWS_CONSTANTS_HPP diff --git a/components/core/src/clp/clg/CMakeLists.txt b/components/core/src/clp/clg/CMakeLists.txt index 37c5a3710..c3c8e3aea 100644 --- a/components/core/src/clp/clg/CMakeLists.txt +++ b/components/core/src/clp/clg/CMakeLists.txt @@ -18,6 +18,8 @@ set( ../ffi/ir_stream/decoding_methods.cpp ../ffi/ir_stream/decoding_methods.hpp ../ffi/ir_stream/decoding_methods.inc + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -31,6 +33,8 @@ set( ../GlobalSQLiteMetadataDB.hpp ../Grep.cpp ../Grep.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp ../ir/parsing.cpp ../ir/parsing.hpp @@ -57,6 +61,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp @@ -124,7 +130,7 @@ target_compile_features(clg PRIVATE cxx_std_20) target_include_directories(clg PRIVATE "${PROJECT_SOURCE_DIR}/submodules") target_link_libraries(clg PRIVATE - Boost::filesystem Boost::iostreams Boost::program_options + Boost::filesystem Boost::program_options fmt::fmt log_surgeon::log_surgeon MariaDBClient::MariaDBClient diff --git a/components/core/src/clp/clg/clg.cpp b/components/core/src/clp/clg/clg.cpp index 4580358b7..dd35a3283 100644 --- a/components/core/src/clp/clg/clg.cpp +++ b/components/core/src/clp/clg/clg.cpp @@ -494,15 +494,13 @@ int main(int argc, char const* argv[]) { if (command_line_args.get_search_strings_file_path().empty()) { search_strings.push_back(command_line_args.get_search_string()); } else { - FileReader file_reader; - file_reader.open(command_line_args.get_search_strings_file_path()); + FileReader file_reader{command_line_args.get_search_strings_file_path()}; string line; while (file_reader.read_to_delimiter('\n', false, false, line)) { if (!line.empty()) { search_strings.push_back(line); } } - file_reader.close(); } // Validate archives directory @@ -589,8 +587,7 @@ int main(int argc, char const* argv[]) { use_heuristic = false; char buf[max_map_schema_length]; - FileReader file_reader; - file_reader.try_open(schema_file_path); + FileReader file_reader{schema_file_path}; size_t num_bytes_read; file_reader.read(buf, max_map_schema_length, num_bytes_read); diff --git a/components/core/src/clp/clo/CMakeLists.txt b/components/core/src/clp/clo/CMakeLists.txt index b798e918a..39cc72b60 100644 --- a/components/core/src/clp/clo/CMakeLists.txt +++ b/components/core/src/clp/clo/CMakeLists.txt @@ -4,6 +4,8 @@ set( ../BufferReader.hpp ../cli_utils.cpp ../cli_utils.hpp + ../clp/FileDecompressor.cpp + ../clp/FileDecompressor.hpp ../database_utils.cpp ../database_utils.hpp ../Defs.h @@ -20,13 +22,23 @@ set( ../ffi/ir_stream/decoding_methods.cpp ../ffi/ir_stream/decoding_methods.hpp ../ffi/ir_stream/decoding_methods.inc + ../ffi/ir_stream/encoding_methods.cpp + ../ffi/ir_stream/encoding_methods.hpp + ../ffi/ir_stream/utils.cpp + ../ffi/ir_stream/utils.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp ../FileWriter.hpp ../Grep.cpp ../Grep.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp + ../ir/LogEventSerializer.cpp + ../ir/LogEventSerializer.hpp ../ir/parsing.cpp ../ir/parsing.hpp ../ir/parsing.inc @@ -49,6 +61,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp @@ -111,6 +125,7 @@ set( clo.cpp CommandLineArguments.cpp CommandLineArguments.hpp + constants.hpp OutputHandler.cpp OutputHandler.hpp ) @@ -143,7 +158,7 @@ target_compile_features(clo PRIVATE cxx_std_20) target_include_directories(clo PRIVATE "${PROJECT_SOURCE_DIR}/submodules") target_link_libraries(clo PRIVATE - Boost::filesystem Boost::iostreams Boost::program_options + Boost::filesystem Boost::program_options fmt::fmt log_surgeon::log_surgeon ${MONGOCXX_TARGET} diff --git a/components/core/src/clp/clo/CommandLineArguments.cpp b/components/core/src/clp/clo/CommandLineArguments.cpp index 8f7d54a12..fffc3d783 100644 --- a/components/core/src/clp/clo/CommandLineArguments.cpp +++ b/components/core/src/clp/clo/CommandLineArguments.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,7 @@ using std::endl; using std::exception; using std::invalid_argument; using std::string; +using std::string_view; using std::vector; namespace clp::clo { @@ -56,6 +58,248 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { ); // clang-format on + po::options_description general_positional_options; + char command_input{}; + general_positional_options.add_options()("command", po::value(&command_input))( + "command-args", + po::value>() + ); + po::positional_options_description general_positional_options_description; + general_positional_options_description.add("command", 1); + general_positional_options_description.add("command-args", -1); + + // Aggregate all options + po::options_description all_options; + all_options.add(options_general); + all_options.add(general_positional_options); + + // Parse options + try { + // Parse options specified on the command line + po::parsed_options parsed = po::command_line_parser(argc, argv) + .options(all_options) + .positional(general_positional_options_description) + .allow_unregistered() + .run(); + po::variables_map parsed_command_line_options; + store(parsed, parsed_command_line_options); + + // Handle config-file manually since Boost won't set it until we call notify, and we can't + // call notify until we parse the config file + if (0 != parsed_command_line_options.count("config-file")) { + config_file_path = parsed_command_line_options["config-file"].as(); + } + + // Parse options specified through the config file + // NOTE: Command line arguments will take priority over config file since they are parsed + // first and Boost doesn't replace existing options + std::ifstream config_file(config_file_path); + if (config_file.is_open()) { + // Allow unrecognized options in configuration file since some of them may be + // exclusively for clp or other applications + po::parsed_options parsed_config_file + = po::parse_config_file(config_file, all_options, true); + store(parsed_config_file, parsed_command_line_options); + config_file.close(); + } + + notify(parsed_command_line_options); + + // Handle --version + if (0 != parsed_command_line_options.count("version")) { + cerr << static_cast(cVersion) << endl; + return ParsingResult::InfoCommand; + } + + // Validate command + if (parsed_command_line_options.count("command") == 0) { + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 2) { + SPDLOG_WARN("Ignoring all options besides --help."); + } + + print_basic_usage(); + cerr << "COMMAND is one of:" << endl; + cerr << " " << enum_to_underlying_type(Command::Search) << " - search" << endl; + cerr << " " << enum_to_underlying_type(Command::ExtractIr) << " - extract IR" + << endl; + cerr << endl; + cerr << "Try " << get_program_name() << " " + << enum_to_underlying_type(Command::Search) << " --help OR " + << get_program_name() << " " << enum_to_underlying_type(Command::ExtractIr) + << " --help for command-specific details." << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration " + "file." + << endl; + po::options_description visible_options; + visible_options.add(options_general); + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } + + throw invalid_argument("COMMAND not specified."); + } + switch (command_input) { + case enum_to_underlying_type(Command::Search): + m_command = static_cast(command_input); + return parse_search_arguments( + options_general, + parsed_command_line_options, + parsed.options, + argc + ); + case enum_to_underlying_type(Command::ExtractIr): + m_command = static_cast(command_input); + return parse_ir_extraction_arguments( + options_general, + parsed_command_line_options, + parsed.options, + argc + ); + default: + throw invalid_argument(string("Unknown command '") + command_input + "'"); + } + } catch (exception& e) { + SPDLOG_ERROR("{}", e.what()); + print_basic_usage(); + cerr << "Try " << get_program_name() << " --help for detailed usage instructions" << endl; + return ParsingResult::Failure; + } +} + +auto CommandLineArguments::parse_ir_extraction_arguments( + po::options_description const& options_general, + po::variables_map& parsed_command_line_options, + vector const& options, + int argc +) -> CommandLineArgumentsBase::ParsingResult { + // Define IR extraction options + po::options_description options_ir_extraction("IR Extraction Options"); + // clang-format off + options_ir_extraction + .add_options()( + "temp-output-dir", + po::value(&m_ir_temp_output_dir)->value_name("DIR"), + "Temporary output directory for IR chunks while they're being written" + )( + "target-size", + po::value(&m_ir_target_size)->value_name("SIZE"), + "Target size (B) for each IR chunk before a new chunk is created" + ); + // clang-format on + + // Define visible options + po::options_description visible_options; + visible_options.add(options_general); + visible_options.add(options_ir_extraction); + + // Define hidden positional options (not shown in Boost's program options help message) + po::options_description hidden_positional_options; + // clang-format off + hidden_positional_options.add_options()( + "archive-path", + po::value(&m_archive_path) + )( + "file-split-id", + po::value(&m_file_split_id) + )( + "output-dir", + po::value(&m_ir_output_dir) + )( + "mongodb-uri", + po::value(&m_ir_mongodb_uri) + )( + "mongodb-collection", + po::value(&m_ir_mongodb_collection) + ); + // clang-format on + po::positional_options_description positional_options_description; + positional_options_description.add("archive-path", 1); + positional_options_description.add("file-split-id", 1); + positional_options_description.add("output-dir", 1); + positional_options_description.add("mongodb-uri", 1); + positional_options_description.add("mongodb-collection", 1); + + // Aggregate all options + po::options_description all_options; + all_options.add(options_ir_extraction); + all_options.add(hidden_positional_options); + + // Parse extraction options + auto extraction_options{po::collect_unrecognized(options, po::include_positional)}; + // Erase the command from the beginning + extraction_options.erase(extraction_options.begin()); + po::store( + po::command_line_parser(extraction_options) + .options(all_options) + .positional(positional_options_description) + .run(), + parsed_command_line_options + ); + notify(parsed_command_line_options); + + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 3) { + SPDLOG_WARN("Ignoring all options besides --help."); + } + + print_ir_extraction_basic_usage(); + cerr << "Examples:" << endl; + cerr << R"( # Extract file (split) with ID "8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4" from)" + << endl; + cerr << R"( # ARCHIVE_PATH as IR into OUTPUT_DIR from ARCHIVE_PATH, and send the metadata)" + << endl; + cerr << R"( # to mongodb://127.0.0.1:27017/test result collection)" << endl; + cerr << " " << get_program_name() + << " i ARCHIVE_PATH 8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4 OUTPUT_DIR " + "mongodb://127.0.0.1:27017/test result" + << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration " + "file." + << endl; + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } + + // Validate input arguments + if (m_archive_path.empty()) { + throw invalid_argument("ARCHIVE_PATH not specified or empty."); + } + + if (m_file_split_id.empty()) { + throw invalid_argument("FILE_SPLIT_ID not specified or empty."); + } + + if (m_ir_output_dir.empty()) { + throw invalid_argument("OUTPUT_DIR not specified or empty."); + } + + if (m_ir_mongodb_uri.empty()) { + throw invalid_argument("URI not specified or empty."); + } + + if (m_ir_mongodb_collection.empty()) { + throw invalid_argument("COLLECTION not specified or empty."); + } + + if (m_ir_temp_output_dir.empty()) { + m_ir_temp_output_dir = m_ir_output_dir; + } + return ParsingResult::Success; +} + +auto CommandLineArguments::parse_search_arguments( + po::options_description const& options_general, + po::variables_map& parsed_command_line_options, + vector const& options, + int argc +) -> CommandLineArgumentsBase::ParsingResult { // Define match controls po::options_description options_match_control("Match Controls"); options_match_control.add_options()( @@ -188,242 +432,185 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { // Aggregate all options po::options_description all_options; - all_options.add(options_general); all_options.add(options_match_control); all_options.add(options_aggregation); all_options.add(hidden_positional_options); // Parse options - try { - // Parse options specified on the command line - po::parsed_options parsed = po::command_line_parser(argc, argv) - .options(all_options) - .positional(positional_options_description) - .allow_unregistered() - .run(); - po::variables_map parsed_command_line_options; - store(parsed, parsed_command_line_options); - - // Handle config-file manually since Boost won't set it until we call notify, and we can't - // call notify until we parse the config file - if (parsed_command_line_options.count("config-file")) { - config_file_path = parsed_command_line_options["config-file"].as(); - } - - // Parse options specified through the config file - // NOTE: Command line arguments will take priority over config file since they are parsed - // first and Boost doesn't replace existing options - std::ifstream config_file(config_file_path); - if (config_file.is_open()) { - // Allow unrecognized options in configuration file since some of them may be - // exclusively for clp or other applications - po::parsed_options parsed_config_file - = po::parse_config_file(config_file, all_options, true); - store(parsed_config_file, parsed_command_line_options); - config_file.close(); - } - - notify(parsed_command_line_options); - - constexpr char cNetworkOutputHandlerName[] = "network"; - constexpr char cReducerOutputHandlerName[] = "reducer"; - constexpr char cResultsCacheOutputHandlerName[] = "results-cache"; - - // Handle --help - if (parsed_command_line_options.count("help")) { - if (argc > 2) { - SPDLOG_WARN("Ignoring all options besides --help."); - } - - print_basic_usage(); - cerr << "OUTPUT_HANDLER is one of:" << endl; - cerr << " " << static_cast(cNetworkOutputHandlerName) - << " - Output to a network destination" << endl; - cerr << " " << static_cast(cResultsCacheOutputHandlerName) - << " - Output to the results cache" << endl; - cerr << " " << static_cast(cReducerOutputHandlerName) - << " - Output to the reducer" << endl; - cerr << endl; - - cerr << "Examples:" << endl; - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to )" - "a network destination" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cNetworkOutputHandlerName) - << " --host localhost --port 18000" << endl; - cerr << endl; - - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and output the results )" - "by performing a count aggregation" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cReducerOutputHandlerName) << " --count" - << " --host localhost --port 14009 --job-id 1" << endl; - cerr << endl; - - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to)" - R"( mongodb://127.0.0.1:27017/test "result" collection )" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cResultsCacheOutputHandlerName) - << R"( --uri mongodb://127.0.0.1:27017/test --collection result)" << endl; - cerr << endl; - - cerr << "Options can be specified on the command line or through a configuration file." - << endl; - cerr << visible_options << endl; - return ParsingResult::InfoCommand; - } - - // Handle --version - if (parsed_command_line_options.count("version")) { - cerr << static_cast(cVersion) << endl; - return ParsingResult::InfoCommand; - } - - // Validate archive path was specified - if (m_archive_path.empty()) { - throw invalid_argument("ARCHIVE_PATH not specified or empty."); - } - - // Validate wildcard string - if (m_search_string.empty()) { - throw invalid_argument("Wildcard string not specified or empty."); + auto search_options{po::collect_unrecognized(options, po::include_positional)}; + search_options.erase(search_options.begin()); + auto parsed{po::command_line_parser(search_options) + .options(all_options) + .positional(positional_options_description) + .allow_unregistered() + .run()}; + po::store(parsed, parsed_command_line_options); + + notify(parsed_command_line_options); + + constexpr string_view cNetworkOutputHandlerName{"network"}; + constexpr string_view cReducerOutputHandlerName{"reducer"}; + constexpr string_view cResultsCacheOutputHandlerName{"results-cache"}; + + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 3) { + SPDLOG_WARN("Ignoring all options besides --help."); } - // Validate timestamp range and compute m_search_begin_ts and m_search_end_ts - if (parsed_command_line_options.count("teq")) { - if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") - + parsed_command_line_options.count("tlt") - + parsed_command_line_options.count("tle") - > 0) - { - throw invalid_argument( - "--teq cannot be specified with any other timestamp filtering option." - ); - } - - m_search_begin_ts = parsed_command_line_options["teq"].as(); - m_search_end_ts = parsed_command_line_options["teq"].as(); - } else { - if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") - > 1) - { - throw invalid_argument("--tgt cannot be used with --tge."); - } - - // Set m_search_begin_ts - if (parsed_command_line_options.count("tgt")) { - m_search_begin_ts = parsed_command_line_options["tgt"].as() + 1; - } else if (parsed_command_line_options.count("tge")) { - m_search_begin_ts = parsed_command_line_options["tge"].as(); - } + print_search_basic_usage(); + cerr << "OUTPUT_HANDLER is one of:" << endl; + cerr << " " << cNetworkOutputHandlerName << " - Output to a network destination" << endl; + cerr << " " << cResultsCacheOutputHandlerName << " - Output to the results cache" << endl; + cerr << " " << cReducerOutputHandlerName << " - Output to the reducer" << endl; + cerr << endl; + + cerr << "Examples:" << endl; + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to )" + "a network destination" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cNetworkOutputHandlerName << " --host localhost --port 18000" << endl; + cerr << endl; + + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and output the results )" + "by performing a count aggregation" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cReducerOutputHandlerName << " --count" + << " --host localhost --port 14009 --job-id 1" << endl; + cerr << endl; + + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to)" + R"( mongodb://127.0.0.1:27017/test "result" collection )" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cResultsCacheOutputHandlerName + << R"( --uri mongodb://127.0.0.1:27017/test --collection result)" << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration file." + << endl; + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } - if (parsed_command_line_options.count("tlt") + parsed_command_line_options.count("tle") - > 1) - { - throw invalid_argument("--tlt cannot be used with --tle."); - } + // Validate archive path was specified + if (m_archive_path.empty()) { + throw invalid_argument("ARCHIVE_PATH not specified or empty."); + } - // Set m_search_end_ts - if (parsed_command_line_options.count("tlt")) { - m_search_end_ts = parsed_command_line_options["tlt"].as() - 1; - } else if (parsed_command_line_options.count("tle")) { - m_search_end_ts = parsed_command_line_options["tle"].as(); - } + // Validate wildcard string + if (m_search_string.empty()) { + throw invalid_argument("Wildcard string not specified or empty."); + } - if (m_search_begin_ts > m_search_end_ts) { - throw invalid_argument( - "Timestamp range is invalid - begin timestamp is after end timestamp." - ); - } + // Validate timestamp range and compute m_search_begin_ts and m_search_end_ts + if (0 != parsed_command_line_options.count("teq")) { + if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") + + parsed_command_line_options.count("tlt") + + parsed_command_line_options.count("tle") + > 0) + { + throw invalid_argument( + "--teq cannot be specified with any other timestamp filtering option." + ); } - // Validate file-path - if (parsed_command_line_options.count("file-path") > 0 && m_file_path.empty()) { - throw invalid_argument("file-path cannot be an empty string."); + m_search_begin_ts = parsed_command_line_options["teq"].as(); + m_search_end_ts = parsed_command_line_options["teq"].as(); + } else { + if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") > 1) + { + throw invalid_argument("--tgt cannot be used with --tge."); } - // Validate count by time bucket size - if (parsed_command_line_options.count("count-by-time") > 0) { - m_do_count_by_time_aggregation = true; - if (m_count_by_time_bucket_size <= 0) { - throw std::invalid_argument("Value for count-by-time must be greater than zero."); - } + // Set m_search_begin_ts + if (0 != parsed_command_line_options.count("tgt")) { + m_search_begin_ts = parsed_command_line_options["tgt"].as() + 1; + } else if (0 != parsed_command_line_options.count("tge")) { + m_search_begin_ts = parsed_command_line_options["tge"].as(); } - // Validate output-handler - if (parsed_command_line_options.count("output-handler") == 0) { - throw invalid_argument("OUTPUT_HANDLER not specified."); - } - if (static_cast(cNetworkOutputHandlerName) == output_handler_name) { - m_output_handler_type = OutputHandlerType::Network; - } else if (static_cast(cReducerOutputHandlerName) == output_handler_name) { - m_output_handler_type = OutputHandlerType::Reducer; - } else if (static_cast(cResultsCacheOutputHandlerName) == output_handler_name) + if (parsed_command_line_options.count("tlt") + parsed_command_line_options.count("tle") > 1) { - m_output_handler_type = OutputHandlerType::ResultsCache; - } else if (output_handler_name.empty()) { - throw invalid_argument("OUTPUT_HANDLER cannot be an empty string."); - } else { - throw invalid_argument("Unknown OUTPUT_HANDLER: " + output_handler_name); + throw invalid_argument("--tlt cannot be used with --tle."); } - switch (m_output_handler_type) { - case OutputHandlerType::Network: - parse_network_dest_output_handler_options( - options_network_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - case OutputHandlerType::Reducer: - parse_reducer_output_handler_options( - options_reducer_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - case OutputHandlerType::ResultsCache: - parse_results_cache_output_handler_options( - options_results_cache_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - default: - throw invalid_argument( - "Unhandled OutputHandlerType=" - + std::to_string(enum_to_underlying_type(m_output_handler_type)) - ); + // Set m_search_end_ts + if (0 != parsed_command_line_options.count("tlt")) { + m_search_end_ts = parsed_command_line_options["tlt"].as() - 1; + } else if (0 != parsed_command_line_options.count("tle")) { + m_search_end_ts = parsed_command_line_options["tle"].as(); } - bool aggregation_was_specified - = m_do_count_by_time_aggregation || m_do_count_results_aggregation; - if (aggregation_was_specified && OutputHandlerType::Reducer != m_output_handler_type) { + if (m_search_begin_ts > m_search_end_ts) { throw invalid_argument( - "Aggregations are only supported with the reducer output handler." + "Timestamp range is invalid - begin timestamp is after end timestamp." ); - } else if ((false == aggregation_was_specified - && OutputHandlerType::Reducer == m_output_handler_type)) - { - throw invalid_argument("The reducer output handler currently only supports count and " - "count-by-time aggregations."); } + } - if (m_do_count_by_time_aggregation && m_do_count_results_aggregation) { - throw std::invalid_argument( - "The --count-by-time and --count options are mutually exclusive." - ); + // Validate file-path + if (parsed_command_line_options.count("file-path") > 0 && m_file_path.empty()) { + throw invalid_argument("file-path cannot be an empty string."); + } + + // Validate count by time bucket size + if (parsed_command_line_options.count("count-by-time") > 0) { + m_do_count_by_time_aggregation = true; + if (m_count_by_time_bucket_size <= 0) { + throw std::invalid_argument("Value for count-by-time must be greater than zero."); } - } catch (exception& e) { - SPDLOG_ERROR("{}", e.what()); - print_basic_usage(); - cerr << "Try " << get_program_name() << " --help for detailed usage instructions" << endl; - return ParsingResult::Failure; } + // Validate output-handler + if (parsed_command_line_options.count("output-handler") == 0) { + throw invalid_argument("OUTPUT_HANDLER not specified."); + } + if (cNetworkOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::Network; + parse_network_dest_output_handler_options( + options_network_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (cReducerOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::Reducer; + parse_reducer_output_handler_options( + options_reducer_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (cResultsCacheOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::ResultsCache; + parse_results_cache_output_handler_options( + options_results_cache_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (output_handler_name.empty()) { + throw invalid_argument("OUTPUT_HANDLER cannot be an empty string."); + } else { + throw invalid_argument("Unknown OUTPUT_HANDLER: " + output_handler_name); + } + + bool const aggregation_was_specified + = m_do_count_by_time_aggregation || m_do_count_results_aggregation; + if (aggregation_was_specified && OutputHandlerType::Reducer != m_output_handler_type) { + throw invalid_argument("Aggregations are only supported with the reducer output handler."); + } + if ((false == aggregation_was_specified && OutputHandlerType::Reducer == m_output_handler_type)) + { + throw invalid_argument("The reducer output handler currently only supports count and " + "count-by-time aggregations."); + } + + if (m_do_count_by_time_aggregation && m_do_count_results_aggregation) { + throw std::invalid_argument( + "The --count-by-time and --count options are mutually exclusive." + ); + } return ParsingResult::Success; } @@ -511,7 +698,18 @@ void CommandLineArguments::parse_results_cache_output_handler_options( } void CommandLineArguments::print_basic_usage() const { - cerr << "Usage: " << get_program_name() << " [OPTIONS]" - << R"( ARCHIVE_PATH "WILDCARD STRING" OUTPUT_HANDLER [OUTPUT_HANDLER_OPTIONS])" << endl; + cerr << "Usage: " << get_program_name() << " [OPTIONS] COMMAND [COMMAND ARGUMENTS]" << endl; +} + +void CommandLineArguments::print_search_basic_usage() const { + cerr << "Usage: " << get_program_name() << " " << enum_to_underlying_type(Command::Search) + << R"( [OPTIONS] ARCHIVE_PATH "WILDCARD STRING" OUTPUT_HANDLER [OUTPUT_HANDLER_OPTIONS])" + << endl; +} + +void CommandLineArguments::print_ir_extraction_basic_usage() const { + cerr << "Usage: " << get_program_name() << " " << enum_to_underlying_type(Command::ExtractIr) + << R"( [OPTIONS] ARCHIVE_PATH FILE_SPLIT_ID OUTPUT_DIR MONGODB_URI MONGODB_COLLECTION)" + << endl; } } // namespace clp::clo diff --git a/components/core/src/clp/clo/CommandLineArguments.hpp b/components/core/src/clp/clo/CommandLineArguments.hpp index 16c88bf26..9e6d311c3 100644 --- a/components/core/src/clp/clo/CommandLineArguments.hpp +++ b/components/core/src/clp/clo/CommandLineArguments.hpp @@ -1,8 +1,10 @@ #ifndef CLP_CLO_COMMANDLINEARGUMENTS_HPP #define CLP_CLO_COMMANDLINEARGUMENTS_HPP +#include #include #include +#include #include #include @@ -18,6 +20,11 @@ namespace clp::clo { class CommandLineArguments : public CommandLineArgumentsBase { public: // Types + enum class Command : char { + Search = 's', + ExtractIr = 'i', + }; + enum class OutputHandlerType : uint8_t { Network = 0, Reducer, @@ -36,8 +43,28 @@ class CommandLineArguments : public CommandLineArgumentsBase { // Methods ParsingResult parse_arguments(int argc, char const* argv[]) override; - std::string const& get_archive_path() const { return m_archive_path; } + [[nodiscard]] auto get_command() const -> Command { return m_command; } + + [[nodiscard]] auto get_archive_path() const -> std::string_view { return m_archive_path; } + + // IR extraction arguments + [[nodiscard]] auto get_file_split_id() const -> std::string const& { return m_file_split_id; } + + [[nodiscard]] size_t get_ir_target_size() const { return m_ir_target_size; } + [[nodiscard]] auto get_ir_output_dir() const -> std::string const& { return m_ir_output_dir; } + + [[nodiscard]] auto get_ir_temp_output_dir() const -> std::string const& { + return m_ir_temp_output_dir; + } + + [[nodiscard]] auto get_ir_mongodb_uri() const -> std::string const& { return m_ir_mongodb_uri; } + + [[nodiscard]] auto get_ir_mongodb_collection() const -> std::string const& { + return m_ir_mongodb_collection; + } + + // Search arguments bool ignore_case() const { return m_ignore_case; } std::string const& get_search_string() const { return m_search_string; } @@ -76,6 +103,40 @@ class CommandLineArguments : public CommandLineArgumentsBase { private: // Methods + /** + * Parses arguments for the search command + * @param options_general + * @param parsed_command_line_options + * @param options + * @param argc + * @return ParsingResult::Success if arguments were parsed without error + * @return ParsingResult::InfoCommand if `--help` was specified + * @throw invalid_argument if invalid arguments are provided + */ + [[nodiscard]] auto parse_search_arguments( + boost::program_options::options_description const& options_general, + boost::program_options::variables_map& parsed_command_line_options, + std::vector const& options, + int argc + ) -> CommandLineArgumentsBase::ParsingResult; + + /** + * Parses arguments for the IR extraction command + * @param options_general + * @param parsed_command_line_options + * @param options + * @param argc + * @return ParsingResult::Success if arguments were parsed without error + * @return ParsingResult::InfoCommand if `--help` was specified + * @throw invalid_argument if invalid arguments are provided + */ + [[nodiscard]] auto parse_ir_extraction_arguments( + boost::program_options::options_description const& options_general, + boost::program_options::variables_map& parsed_command_line_options, + std::vector const& options, + int argc + ) -> CommandLineArgumentsBase::ParsingResult; + /** * Validates output options related to the Network Destination output handler. * @param options_description @@ -116,9 +177,21 @@ class CommandLineArguments : public CommandLineArgumentsBase { ); void print_basic_usage() const override; + void print_search_basic_usage() const; + void print_ir_extraction_basic_usage() const; - // Search variables + Command m_command; std::string m_archive_path; + + // Variables for IR extraction + std::string m_file_split_id; + size_t m_ir_target_size{128ULL * 1024 * 1024}; + std::string m_ir_output_dir; + std::string m_ir_temp_output_dir; + std::string m_ir_mongodb_uri; + std::string m_ir_mongodb_collection; + + // Variables for search bool m_ignore_case; std::string m_search_string; std::string m_file_path; diff --git a/components/core/src/clp/clo/OutputHandler.cpp b/components/core/src/clp/clo/OutputHandler.cpp index 3cc5de4e0..bdf1bb1bd 100644 --- a/components/core/src/clp/clo/OutputHandler.cpp +++ b/components/core/src/clp/clo/OutputHandler.cpp @@ -10,6 +10,7 @@ #include "../../reducer/CountOperator.hpp" #include "../../reducer/network_utils.hpp" #include "../networking/socket_utils.hpp" +#include "constants.hpp" using clp::streaming_archive::reader::Message; using std::string; @@ -99,13 +100,25 @@ ErrorCode ResultsCacheOutputHandler::flush() { try { m_results.emplace_back(std::move(bsoncxx::builder::basic::make_document( bsoncxx::builder::basic::kvp( - "orig_file_path", + cResultsCacheKeys::OrigFileId, + std::move(result.orig_file_id) + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::OrigFilePath, std::move(result.orig_file_path) ), - bsoncxx::builder::basic::kvp("orig_file_id", std::move(result.orig_file_id)), - bsoncxx::builder::basic::kvp("log_event_ix", result.log_event_ix), - bsoncxx::builder::basic::kvp("timestamp", result.timestamp), - bsoncxx::builder::basic::kvp("message", std::move(result.decompressed_message)) + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::LogEventIx, + result.log_event_ix + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::Timestamp, + result.timestamp + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::Message, + std::move(result.decompressed_message) + ) ))); count++; diff --git a/components/core/src/clp/clo/OutputHandler.hpp b/components/core/src/clp/clo/OutputHandler.hpp index 06c982c09..5f8b71f7d 100644 --- a/components/core/src/clp/clo/OutputHandler.hpp +++ b/components/core/src/clp/clo/OutputHandler.hpp @@ -56,7 +56,7 @@ class OutputHandler { * metadata about the file */ [[nodiscard]] virtual bool can_skip_file( - [[maybe_unused]] clp::streaming_archive::MetadataDB::FileIterator const& it + [[maybe_unused]] ::clp::streaming_archive::MetadataDB::FileIterator const& it ) { return false; } @@ -187,7 +187,7 @@ class ResultsCacheOutputHandler : public OutputHandler { */ ErrorCode flush() override; - [[nodiscard]] bool can_skip_file(clp::streaming_archive::MetadataDB::FileIterator const& it + [[nodiscard]] bool can_skip_file(::clp::streaming_archive::MetadataDB::FileIterator const& it ) override { return is_latest_results_full() && get_smallest_timestamp() > it.get_end_ts(); } diff --git a/components/core/src/clp/clo/clo.cpp b/components/core/src/clp/clo/clo.cpp index f0328dc3f..f29df0306 100644 --- a/components/core/src/clp/clo/clo.cpp +++ b/components/core/src/clp/clo/clo.cpp @@ -1,16 +1,21 @@ +#include #include #include +#include -#include #include #include #include "../../reducer/network_utils.hpp" +#include "../clp/FileDecompressor.hpp" #include "../Defs.h" #include "../Grep.hpp" +#include "../ir/constants.hpp" #include "../Profiler.hpp" #include "../spdlog_with_specializations.hpp" +#include "../Utils.hpp" #include "CommandLineArguments.hpp" +#include "constants.hpp" #include "OutputHandler.hpp" using clp::clo::CommandLineArguments; @@ -19,12 +24,17 @@ using clp::clo::CountOutputHandler; using clp::clo::NetworkOutputHandler; using clp::clo::OutputHandler; using clp::clo::ResultsCacheOutputHandler; +using clp::clp::FileDecompressor; using clp::CommandLineArgumentsBase; +using clp::create_directory; using clp::epochtime_t; using clp::ErrorCode; +using clp::ErrorCode_BadParam_DB_URI; using clp::ErrorCode_errno; +using clp::ErrorCode_FileExists; using clp::ErrorCode_Success; using clp::Grep; +using clp::ir::cIrFileExtension; using clp::load_lexer_from_file; using clp::Query; using clp::streaming_archive::MetadataDB; @@ -35,6 +45,7 @@ using clp::TraceableException; using std::cerr; using std::cout; using std::endl; +using std::runtime_error; using std::string; using std::to_string; using std::unique_ptr; @@ -81,16 +92,279 @@ static void search_files( /** * Searches an archive with the given path * @param command_line_args - * @param archive_path * @param output_handler * @return true on success, false otherwise */ static bool search_archive( CommandLineArguments const& command_line_args, - boost::filesystem::path const& archive_path, std::unique_ptr output_handler ); +namespace { +/** + * Extracts a file split as IR chunks, writing them to the local filesystem and writing their + * metadata to the results cache. + * @param command_line_args + * @return Whether the file split was successfully extracted. + */ +bool extract_ir(CommandLineArguments const& command_line_args); + +/** + * Performs a searches acccording to the given arguments. + * @param command_line_args + * @return Whether the search was successful. + */ +bool search(CommandLineArguments const& command_line_args); + +/** + * @param archive_path + * @return Whether the given path exists and contains an archive metadata file. + */ +bool validate_archive_path(std::filesystem::path const& archive_path); + +bool extract_ir(CommandLineArguments const& command_line_args) { + std::filesystem::path const archive_path{command_line_args.get_archive_path()}; + if (false == validate_archive_path(archive_path)) { + return false; + } + + try { + // Create output directory in case it doesn't exist + std::filesystem::path const output_dir{command_line_args.get_ir_output_dir()}; + if (auto const error_code = create_directory(output_dir.string(), 0700, true); + ErrorCode_Success != error_code) + { + SPDLOG_ERROR("Failed to create {} - {}", output_dir.string(), strerror(errno)); + return false; + } + + Archive archive_reader; + archive_reader.open(archive_path.string()); + archive_reader.refresh_dictionaries(); + + auto const& file_split_id = command_line_args.get_file_split_id(); + auto file_metadata_ix_ptr = archive_reader.get_file_iterator_by_split_id(file_split_id); + if (false == file_metadata_ix_ptr->has_next()) { + SPDLOG_ERROR( + "File split '{}' doesn't exist in archive '{}'", + file_split_id, + archive_path.string() + ); + return false; + } + + mongocxx::client client; + mongocxx::collection collection; + + try { + auto const mongo_uri{mongocxx::uri(command_line_args.get_ir_mongodb_uri())}; + client = mongocxx::client{mongo_uri}; + collection + = client[mongo_uri.database()][command_line_args.get_ir_mongodb_collection()]; + } catch (mongocxx::exception const& e) { + SPDLOG_ERROR("Failed to connect to results cache - {}", e.what()); + return false; + } + + std::vector results; + auto ir_output_handler = [&](std::filesystem::path const& src_ir_path, + string const& orig_file_id, + size_t begin_message_ix, + size_t end_message_ix, + bool is_last_ir_chunk) { + auto dest_ir_file_name = orig_file_id; + dest_ir_file_name += "_" + std::to_string(begin_message_ix); + dest_ir_file_name += "_" + std::to_string(end_message_ix); + dest_ir_file_name += cIrFileExtension; + + auto const dest_ir_path = output_dir / dest_ir_file_name; + try { + std::filesystem::rename(src_ir_path, dest_ir_path); + } catch (std::filesystem::filesystem_error const& e) { + SPDLOG_ERROR( + "Failed to rename '{}' to '{}' - {}", + src_ir_path.string(), + dest_ir_path.string(), + e.what() + ); + return false; + } + results.emplace_back(std::move(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::Path, + dest_ir_file_name + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::OrigFileId, + orig_file_id + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::FileSplitId, + file_split_id + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::BeginMsgIx, + static_cast(begin_message_ix) + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::EndMsgIx, + static_cast(end_message_ix) + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::IsLastIrChunk, + is_last_ir_chunk + ) + ))); + return true; + }; + + FileDecompressor file_decompressor; + if (false + == file_decompressor.decompress_to_ir( + archive_reader, + *file_metadata_ix_ptr, + command_line_args.get_ir_target_size(), + command_line_args.get_ir_temp_output_dir(), + ir_output_handler + )) + { + return false; + } + + // Write the metadata into the results cache + if (false == results.empty()) { + try { + collection.insert_many(results); + } catch (mongocxx::exception const& e) { + SPDLOG_ERROR("Failed to insert results into results cache - {}", e.what()); + return false; + } + results.clear(); + } + + file_metadata_ix_ptr.reset(nullptr); + + archive_reader.close(); + } catch (TraceableException& e) { + auto error_code = e.get_error_code(); + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR( + "IR extraction failed: {}:{} {}, errno={}", + e.get_filename(), + e.get_line_number(), + e.what(), + errno + ); + } else { + SPDLOG_ERROR( + "IR extraction failed: {}:{} {}, error_code={}", + e.get_filename(), + e.get_line_number(), + e.what(), + error_code + ); + } + return false; + } + + return true; +} + +bool search(CommandLineArguments const& command_line_args) { + std::unique_ptr output_handler; + try { + switch (command_line_args.get_output_handler_type()) { + case CommandLineArguments::OutputHandlerType::Network: + output_handler = std::make_unique( + command_line_args.get_network_dest_host(), + command_line_args.get_network_dest_port() + ); + break; + case CommandLineArguments::OutputHandlerType::Reducer: { + auto const reducer_socket_fd = reducer::connect_to_reducer( + command_line_args.get_reducer_host(), + command_line_args.get_reducer_port(), + command_line_args.get_job_id() + ); + if (-1 == reducer_socket_fd) { + SPDLOG_ERROR("Failed to connect to reducer"); + return false; + } + + if (command_line_args.do_count_results_aggregation()) { + output_handler = std::make_unique(reducer_socket_fd); + } else if (command_line_args.do_count_by_time_aggregation()) { + output_handler = std::make_unique( + reducer_socket_fd, + command_line_args.get_count_by_time_bucket_size() + ); + } else { + SPDLOG_ERROR("Unhandled aggregation type."); + return false; + } + + break; + } + case CommandLineArguments::OutputHandlerType::ResultsCache: + output_handler = std::make_unique( + command_line_args.get_mongodb_uri(), + command_line_args.get_mongodb_collection(), + command_line_args.get_batch_size(), + command_line_args.get_max_num_results() + ); + break; + default: + SPDLOG_ERROR("Unhandled OutputHandlerType."); + return false; + } + } catch (clp::TraceableException& e) { + SPDLOG_ERROR("Failed to create output handler - {}", e.what()); + return false; + } + + try { + return search_archive(command_line_args, std::move(output_handler)); + } catch (TraceableException& e) { + auto error_code = e.get_error_code(); + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR( + "Search failed: {}:{} {}, errno={}", + e.get_filename(), + e.get_line_number(), + e.what(), + errno + ); + } else { + SPDLOG_ERROR( + "Search failed: {}:{} {}, error_code={}", + e.get_filename(), + e.get_line_number(), + e.what(), + error_code + ); + } + return false; + } +} + +bool validate_archive_path(std::filesystem::path const& archive_path) { + if (false == std::filesystem::exists(archive_path)) { + SPDLOG_ERROR("Archive '{}' doesn't exist.", archive_path.string()); + return false; + } + auto const archive_metadata_file = archive_path / clp::streaming_archive::cMetadataFileName; + if (false == std::filesystem::exists(archive_metadata_file)) { + SPDLOG_ERROR( + "Archive metadata file '{}' doesn't exist. '{}' may not be an archive.", + archive_metadata_file.string(), + archive_path.string() + ); + return false; + } + return true; +} +} // namespace + static SearchFilesResult search_file( Query& query, Archive& archive, @@ -184,20 +458,10 @@ void search_files( static bool search_archive( CommandLineArguments const& command_line_args, - boost::filesystem::path const& archive_path, std::unique_ptr output_handler ) { - if (false == boost::filesystem::exists(archive_path)) { - SPDLOG_ERROR("Archive '{}' does not exist.", archive_path.c_str()); - return false; - } - auto archive_metadata_file = archive_path / clp::streaming_archive::cMetadataFileName; - if (false == boost::filesystem::exists(archive_metadata_file)) { - SPDLOG_ERROR( - "Archive metadata file '{}' does not exist. '{}' may not be an archive.", - archive_metadata_file.c_str(), - archive_path.c_str() - ); + std::filesystem::path const archive_path{command_line_args.get_archive_path()}; + if (false == validate_archive_path(archive_path)) { return false; } @@ -205,7 +469,7 @@ static bool search_archive( auto schema_file_path = archive_path / clp::streaming_archive::cSchemaFileName; unique_ptr forward_lexer, reverse_lexer; bool use_heuristic = true; - if (boost::filesystem::exists(schema_file_path)) { + if (std::filesystem::exists(schema_file_path)) { use_heuristic = false; // Create forward lexer forward_lexer.reset(new log_surgeon::lexers::ByteLexer()); @@ -302,85 +566,21 @@ int main(int argc, char const* argv[]) { break; } + // mongocxx static init mongocxx::instance mongocxx_instance{}; - std::unique_ptr output_handler; - try { - switch (command_line_args.get_output_handler_type()) { - case CommandLineArguments::OutputHandlerType::Network: - output_handler = std::make_unique( - command_line_args.get_network_dest_host(), - command_line_args.get_network_dest_port() - ); - break; - case CommandLineArguments::OutputHandlerType::Reducer: { - auto reducer_socket_fd = reducer::connect_to_reducer( - command_line_args.get_reducer_host(), - command_line_args.get_reducer_port(), - command_line_args.get_job_id() - ); - if (-1 == reducer_socket_fd) { - SPDLOG_ERROR("Failed to connect to reducer"); - return -1; - } - - if (command_line_args.do_count_results_aggregation()) { - output_handler = std::make_unique(reducer_socket_fd); - } else if (command_line_args.do_count_by_time_aggregation()) { - output_handler = std::make_unique( - reducer_socket_fd, - command_line_args.get_count_by_time_bucket_size() - ); - } else { - SPDLOG_ERROR("Unhandled aggregation type."); - return -1; - } - - break; - } - case CommandLineArguments::OutputHandlerType::ResultsCache: - output_handler = std::make_unique( - command_line_args.get_mongodb_uri(), - command_line_args.get_mongodb_collection(), - command_line_args.get_batch_size(), - command_line_args.get_max_num_results() - ); - break; - default: - SPDLOG_ERROR("Unhandled OutputHandlerType."); - return -1; + auto const& command = command_line_args.get_command(); + if (CommandLineArguments::Command::Search == command) { + if (false == search(command_line_args)) { + return -1; } - } catch (clp::TraceableException& e) { - SPDLOG_ERROR("Failed to create output handler - {}", e.what()); + } else if (CommandLineArguments::Command::ExtractIr == command) { + if (false == extract_ir(command_line_args)) { + return -1; + } + } else { + SPDLOG_ERROR("Command {} not implemented.", clp::enum_to_underlying_type(command)); return -1; } - auto const archive_path = boost::filesystem::path(command_line_args.get_archive_path()); - - int return_value = 0; - try { - if (false == search_archive(command_line_args, archive_path, std::move(output_handler))) { - return_value = -1; - } - } catch (TraceableException& e) { - auto error_code = e.get_error_code(); - if (ErrorCode_errno == error_code) { - SPDLOG_ERROR( - "Search failed: {}:{} {}, errno={}", - e.get_filename(), - e.get_line_number(), - e.what(), - errno - ); - } else { - SPDLOG_ERROR( - "Search failed: {}:{} {}, error_code={}", - e.get_filename(), - e.get_line_number(), - e.what(), - error_code - ); - } - return_value = -1; - } - return return_value; + return 0; } diff --git a/components/core/src/clp/clo/constants.hpp b/components/core/src/clp/clo/constants.hpp new file mode 100644 index 000000000..86f7313f2 --- /dev/null +++ b/components/core/src/clp/clo/constants.hpp @@ -0,0 +1,26 @@ +#ifndef CLP_CLO_CONSTANTS_HPP +#define CLP_CLO_CONSTANTS_HPP + +// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays, readability-identifier-naming) +namespace clp::clo::cResultsCacheKeys { +constexpr char OrigFileId[]{"orig_file_id"}; + +namespace IrOutput { +constexpr char Path[]{"path"}; +constexpr char FileSplitId[]{"file_split_id"}; +constexpr char BeginMsgIx[]{"begin_msg_ix"}; +constexpr char EndMsgIx[]{"end_msg_ix"}; +constexpr char IsLastIrChunk[]{"is_last_ir_chunk"}; +} // namespace IrOutput + +namespace SearchOutput { +constexpr char OrigFilePath[]{"orig_file_path"}; +constexpr char LogEventIx[]{"log_event_ix"}; +constexpr char Timestamp[]{"timestamp"}; +constexpr char Message[]{"message"}; +} // namespace SearchOutput +} // namespace clp::clo::cResultsCacheKeys + +// NOLINTEND(cppcoreguidelines-avoid-c-arrays, readability-identifier-naming) + +#endif // CLP_CLO_CONSTANTS_HPP diff --git a/components/core/src/clp/clp/CMakeLists.txt b/components/core/src/clp/clp/CMakeLists.txt index 1d8a20860..eff32ce46 100644 --- a/components/core/src/clp/clp/CMakeLists.txt +++ b/components/core/src/clp/clp/CMakeLists.txt @@ -25,6 +25,10 @@ set( ../ffi/ir_stream/decoding_methods.inc ../ffi/ir_stream/encoding_methods.cpp ../ffi/ir_stream/encoding_methods.hpp + ../ffi/ir_stream/utils.cpp + ../ffi/ir_stream/utils.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -37,6 +41,8 @@ set( ../GlobalSQLiteMetadataDB.cpp ../GlobalSQLiteMetadataDB.hpp ../ir/constants.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp ../ir/LogEventDeserializer.cpp ../ir/LogEventDeserializer.hpp @@ -78,6 +84,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp @@ -127,6 +135,8 @@ set( ../TimestampPattern.hpp ../TraceableException.hpp ../type_utils.hpp + ../utf8_utils.cpp + ../utf8_utils.hpp ../Utils.cpp ../Utils.hpp ../VariableDictionaryEntry.cpp @@ -161,7 +171,7 @@ target_compile_features(clp PRIVATE cxx_std_20) target_include_directories(clp PRIVATE "${PROJECT_SOURCE_DIR}/submodules") target_link_libraries(clp PRIVATE - Boost::filesystem Boost::iostreams Boost::program_options + Boost::filesystem Boost::program_options fmt::fmt log_surgeon::log_surgeon spdlog::spdlog diff --git a/components/core/src/clp/clp/FileCompressor.cpp b/components/core/src/clp/clp/FileCompressor.cpp index 4100816f5..9898602cc 100644 --- a/components/core/src/clp/clp/FileCompressor.cpp +++ b/components/core/src/clp/clp/FileCompressor.cpp @@ -16,6 +16,7 @@ #include "../LogSurgeonReader.hpp" #include "../Profiler.hpp" #include "../streaming_archive/writer/utils.hpp" +#include "../utf8_utils.hpp" #include "utils.hpp" using clp::ir::eight_byte_encoded_variable_t; @@ -145,8 +146,8 @@ bool FileCompressor::compress_file( size_t peek_size{0}; m_file_reader.peek_buffered_data(utf8_validation_buf, peek_size); bool succeeded = true; - auto utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); - if (is_utf8_sequence(utf8_validation_buf_len, utf8_validation_buf)) { + auto const utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); + if (is_utf8_encoded({utf8_validation_buf, utf8_validation_buf_len})) { if (use_heuristic) { parse_and_encode_with_heuristic( target_data_size_of_dicts, @@ -359,8 +360,8 @@ bool FileCompressor::try_compressing_as_archive( size_t peek_size{0}; m_libarchive_file_reader.peek_buffered_data(utf8_validation_buf, peek_size); string file_path{m_libarchive_reader.get_path()}; - auto utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); - if (is_utf8_sequence(utf8_validation_buf_len, utf8_validation_buf)) { + auto const utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); + if (is_utf8_encoded({utf8_validation_buf, utf8_validation_buf_len})) { auto boost_path_for_compression = parent_boost_path / file_path; if (use_heuristic) { parse_and_encode_with_heuristic( diff --git a/components/core/src/clp/clp/FileDecompressor.hpp b/components/core/src/clp/clp/FileDecompressor.hpp index 6bcf86829..932cab7c5 100644 --- a/components/core/src/clp/clp/FileDecompressor.hpp +++ b/components/core/src/clp/clp/FileDecompressor.hpp @@ -3,10 +3,9 @@ #include #include +#include #include -#include - #include "../FileWriter.hpp" #include "../ir/constants.hpp" #include "../ir/LogEventSerializer.hpp" @@ -39,8 +38,8 @@ class FileDecompressor { * new IR chunk when the current IR chunk exceeds ir_target_size. * * @tparam IrOutputHandler Function to handle the resulting IR chunks. - * Signature: (boost::filesystem::path const& ir_file_path, string const& orig_file_id, - * size_t begin_message_ix, size_t end_message_ix) -> bool; + * Signature: (std::filesystem::path const& ir_file_path, string const& orig_file_id, + * size_t begin_message_ix, size_t end_message_ix, bool is_last_ir_chunk) -> bool; * The function returns whether it succeeded. * @param archive_reader * @param file_metadata_ix @@ -99,7 +98,7 @@ auto FileDecompressor::decompress_to_ir( return false; } - boost::filesystem::path ir_output_path{output_dir}; + std::filesystem::path ir_output_path{output_dir}; auto ir_file_name = m_encoded_file.get_id_as_string(); ir_file_name += ir::cIrFileExtension; ir_output_path /= ir_file_name; @@ -132,7 +131,8 @@ auto FileDecompressor::decompress_to_ir( ir_output_path, file_orig_id, begin_message_ix, - end_message_ix + end_message_ix, + false )) { return false; @@ -162,7 +162,8 @@ auto FileDecompressor::decompress_to_ir( auto const end_message_ix = begin_message_ix + ir_serializer.get_num_log_events(); ir_serializer.close(); - if (false == ir_output_handler(ir_output_path, file_orig_id, begin_message_ix, end_message_ix)) + if (false + == ir_output_handler(ir_output_path, file_orig_id, begin_message_ix, end_message_ix, true)) { return false; } diff --git a/components/core/src/clp/clp/compression.cpp b/components/core/src/clp/clp/compression.cpp index 1741557bc..a0d5bf276 100644 --- a/components/core/src/clp/clp/compression.cpp +++ b/components/core/src/clp/clp/compression.cpp @@ -1,6 +1,7 @@ #include "compression.hpp" #include +#include #include #include @@ -19,8 +20,10 @@ using clp::streaming_archive::writer::split_archive; using std::cerr; using std::cout; using std::endl; +using std::make_unique; using std::out_of_range; using std::string; +using std::unique_ptr; using std::vector; namespace clp::clp { @@ -192,9 +195,11 @@ bool read_and_validate_grouped_file_list( string const& list_path, vector& grouped_files ) { - FileReader grouped_file_path_reader; - ErrorCode error_code = grouped_file_path_reader.try_open(list_path); - if (ErrorCode_Success != error_code) { + unique_ptr grouped_file_path_reader; + try { + grouped_file_path_reader = make_unique(list_path); + } catch (FileReader::OperationFailed const& exception) { + auto const error_code = exception.get_error_code(); if (ErrorCode_FileNotFound == error_code) { SPDLOG_ERROR("'{}' does not exist.", list_path.c_str()); } else if (ErrorCode_errno == error_code) { @@ -205,10 +210,12 @@ bool read_and_validate_grouped_file_list( return false; } - FileReader grouped_file_id_reader; + unique_ptr grouped_file_id_reader; string grouped_file_ids_path = list_path.substr(0, list_path.length() - 4) + ".gid"; - error_code = grouped_file_id_reader.try_open(grouped_file_ids_path); - if (ErrorCode_Success != error_code) { + try { + grouped_file_id_reader = make_unique(grouped_file_ids_path); + } catch (FileReader::OperationFailed const& exception) { + auto const error_code = exception.get_error_code(); if (ErrorCode_FileNotFound == error_code) { SPDLOG_ERROR("'{}' does not exist.", grouped_file_ids_path.c_str()); } else if (ErrorCode_errno == error_code) { @@ -228,9 +235,10 @@ bool read_and_validate_grouped_file_list( string path; string path_without_prefix; group_id_t group_id; + ErrorCode error_code{ErrorCode_Success}; while (true) { // Read path - error_code = grouped_file_path_reader.try_read_to_delimiter('\n', false, false, path); + error_code = grouped_file_path_reader->try_read_to_delimiter('\n', false, false, path); if (ErrorCode_Success != error_code) { break; } @@ -242,7 +250,7 @@ bool read_and_validate_grouped_file_list( } // Read group ID - error_code = grouped_file_id_reader.try_read_numeric_value(group_id); + error_code = grouped_file_id_reader->try_read_numeric_value(group_id); if (ErrorCode_Success != error_code) { if (ErrorCode_EndOfFile == error_code) { SPDLOG_ERROR("There are more grouped file paths than IDs."); @@ -294,9 +302,6 @@ bool read_and_validate_grouped_file_list( return false; } - grouped_file_path_reader.close(); - grouped_file_id_reader.close(); - // Validate the list contained at least one file if (grouped_files.empty()) { SPDLOG_ERROR("'{}' did not contain any paths.", list_path.c_str()); diff --git a/components/core/src/clp/clp/decompression.cpp b/components/core/src/clp/clp/decompression.cpp index 5eb8fc898..6b87f6777 100644 --- a/components/core/src/clp/clp/decompression.cpp +++ b/components/core/src/clp/clp/decompression.cpp @@ -1,10 +1,8 @@ #include "decompression.hpp" +#include #include -#include -#include - #include "../ErrorCode.hpp" #include "../FileWriter.hpp" #include "../GlobalMySQLMetadataDB.hpp" @@ -31,7 +29,7 @@ bool decompress( ErrorCode error_code; // Create output directory in case it doesn't exist - auto output_dir = boost::filesystem::path(command_line_args.get_output_dir()); + auto output_dir = std::filesystem::path(command_line_args.get_output_dir()); error_code = create_directory(output_dir.parent_path().string(), 0700, true); if (ErrorCode_Success != error_code) { SPDLOG_ERROR("Failed to create {} - {}", output_dir.parent_path().c_str(), strerror(errno)); @@ -41,13 +39,13 @@ bool decompress( unordered_set decompressed_files; try { - auto archives_dir = boost::filesystem::path(command_line_args.get_archives_dir()); + auto archives_dir = std::filesystem::path(command_line_args.get_archives_dir()); auto const& global_metadata_db_config = command_line_args.get_metadata_db_config(); auto global_metadata_db = get_global_metadata_db(global_metadata_db_config, archives_dir); streaming_archive::reader::Archive archive_reader; - boost::filesystem::path empty_directory_path; + std::filesystem::path empty_directory_path; FileDecompressor file_decompressor; @@ -65,7 +63,7 @@ bool decompress( archive_ix->get_id(archive_id); auto archive_path = archives_dir / archive_id; - if (false == boost::filesystem::exists(archive_path)) { + if (false == std::filesystem::exists(archive_path)) { SPDLOG_WARN( "Archive {} does not exist in '{}'.", archive_id, @@ -181,11 +179,11 @@ bool decompress( global_metadata_db->close(); string final_path; - boost::system::error_code boost_error_code; + std::error_code std_error_code; for (auto const& temp_path_and_final_path : temp_path_to_final_path) { final_path = temp_path_and_final_path.second; for (size_t i = 1; i < SIZE_MAX; ++i) { - if (boost::filesystem::exists(final_path, boost_error_code)) { + if (std::filesystem::exists(final_path, std_error_code)) { final_path = temp_path_and_final_path.second; final_path += '.'; final_path += std::to_string(i); @@ -238,7 +236,7 @@ bool decompress_to_ir(CommandLineArguments& command_line_args) { ErrorCode error_code{}; // Create output directory in case it doesn't exist - auto output_dir = boost::filesystem::path(command_line_args.get_output_dir()); + std::filesystem::path output_dir{command_line_args.get_output_dir()}; error_code = create_directory(output_dir.parent_path().string(), 0700, true); if (ErrorCode_Success != error_code) { SPDLOG_ERROR("Failed to create {} - {}", output_dir.parent_path().c_str(), strerror(errno)); @@ -246,7 +244,7 @@ bool decompress_to_ir(CommandLineArguments& command_line_args) { } try { - auto archives_dir = boost::filesystem::path(command_line_args.get_archives_dir()); + std::filesystem::path archives_dir{command_line_args.get_archives_dir()}; auto const& global_metadata_db_config = command_line_args.get_metadata_db_config(); auto global_metadata_db = get_global_metadata_db(global_metadata_db_config, archives_dir); @@ -280,10 +278,11 @@ bool decompress_to_ir(CommandLineArguments& command_line_args) { return false; } - auto ir_output_handler = [&](boost::filesystem::path const& src_ir_path, + auto ir_output_handler = [&](std::filesystem::path const& src_ir_path, string const& orig_file_id, size_t begin_message_ix, - size_t end_message_ix) { + size_t end_message_ix, + [[maybe_unused]] bool is_last_ir_chunk) { auto dest_ir_file_name = orig_file_id; dest_ir_file_name += "_" + std::to_string(begin_message_ix); dest_ir_file_name += "_" + std::to_string(end_message_ix); @@ -291,8 +290,8 @@ bool decompress_to_ir(CommandLineArguments& command_line_args) { auto const dest_ir_path = output_dir / dest_ir_file_name; try { - boost::filesystem::rename(src_ir_path, dest_ir_path); - } catch (boost::filesystem::filesystem_error const& e) { + std::filesystem::rename(src_ir_path, dest_ir_path); + } catch (std::filesystem::filesystem_error const& e) { SPDLOG_ERROR( "Failed to rename from {} to {}. Error: {}", src_ir_path.c_str(), diff --git a/components/core/src/clp/clp/utils.cpp b/components/core/src/clp/clp/utils.cpp index 77ee4c344..0f05d75ac 100644 --- a/components/core/src/clp/clp/utils.cpp +++ b/components/core/src/clp/clp/utils.cpp @@ -1,5 +1,6 @@ #include "utils.hpp" +#include #include #include @@ -85,40 +86,6 @@ bool find_all_files_and_empty_directories( return true; } -bool is_utf8_sequence(size_t sequence_length, char const* sequence) { - size_t num_utf8_bytes_to_read = 0; - for (size_t i = 0; i < sequence_length; ++i) { - auto byte = sequence[i]; - - if (num_utf8_bytes_to_read > 0) { - // Validate that byte matches 0b10xx_xxxx - if ((byte & 0xC0) != 0x80) { - return false; - } - --num_utf8_bytes_to_read; - } else { - if (byte & 0x80) { - // Check if byte is valid UTF-8 length-indicator - if ((byte & 0xF8) == 0xF0) { - // Matches 0b1111_0xxx - num_utf8_bytes_to_read = 3; - } else if ((byte & 0xF0) == 0xE0) { - // Matches 0b1110_xxxx - num_utf8_bytes_to_read = 2; - } else if ((byte & 0xE0) == 0xC0) { - // Matches 0b110x_xxxx - num_utf8_bytes_to_read = 1; - } else { - // Invalid UTF-8 length-indicator - return false; - } - } // else byte is ASCII - } - } - - return true; -} - bool read_input_paths(string const& list_path, vector& paths) { ErrorCode error_code = read_list_of_paths(list_path, paths); if (ErrorCode_Success != error_code) { @@ -207,7 +174,7 @@ bool validate_paths_exist(vector const& paths) { std::unique_ptr get_global_metadata_db( GlobalMetadataDBConfig const& global_metadata_db_config, - boost::filesystem::path const& archives_dir + std::filesystem::path const& archives_dir ) { switch (global_metadata_db_config.get_metadata_db_type()) { case GlobalMetadataDBConfig::MetadataDBType::SQLite: { diff --git a/components/core/src/clp/clp/utils.hpp b/components/core/src/clp/clp/utils.hpp index 4f58e1ee4..0a6918445 100644 --- a/components/core/src/clp/clp/utils.hpp +++ b/components/core/src/clp/clp/utils.hpp @@ -1,6 +1,7 @@ #ifndef CLP_CLP_UTILS_HPP #define CLP_CLP_UTILS_HPP +#include #include #include @@ -40,14 +41,6 @@ bool find_all_files_and_empty_directories( std::vector& empty_directory_paths ); -/** - * Checks if the given sequence is valid UTF-8 - * @param sequence_length - * @param sequence - * @return true if valid, false otherwise - */ -bool is_utf8_sequence(size_t sequence_length, char const* sequence); - /** * Reads a list of input paths * @param list_path @@ -87,7 +80,7 @@ bool validate_paths_exist(std::vector const& paths); */ std::unique_ptr get_global_metadata_db( GlobalMetadataDBConfig const& global_metadata_db_config, - boost::filesystem::path const& archives_dir + std::filesystem::path const& archives_dir ); } // namespace clp::clp diff --git a/components/core/src/clp/dictionary_utils.cpp b/components/core/src/clp/dictionary_utils.cpp index 2fecd7e04..d8bd20a9a 100644 --- a/components/core/src/clp/dictionary_utils.cpp +++ b/components/core/src/clp/dictionary_utils.cpp @@ -1,35 +1,12 @@ #include "dictionary_utils.hpp" -namespace clp { -void open_dictionary_for_reading( - std::string const& dictionary_path, - std::string const& segment_index_path, - size_t decompressor_file_read_buffer_capacity, - FileReader& dictionary_file_reader, - streaming_compression::Decompressor& dictionary_decompressor, - FileReader& segment_index_file_reader, - streaming_compression::Decompressor& segment_index_decompressor -) { - dictionary_file_reader.open(dictionary_path); - // Skip header - dictionary_file_reader.seek_from_begin(sizeof(uint64_t)); - // Open decompressor - dictionary_decompressor.open(dictionary_file_reader, decompressor_file_read_buffer_capacity); - - segment_index_file_reader.open(segment_index_path); - // Skip header - segment_index_file_reader.seek_from_begin(sizeof(uint64_t)); - // Open decompressor - segment_index_decompressor.open( - segment_index_file_reader, - decompressor_file_read_buffer_capacity - ); -} +#include "FileReader.hpp" +namespace clp { uint64_t read_dictionary_header(FileReader& file_reader) { auto dictionary_file_reader_pos = file_reader.get_pos(); file_reader.seek_from_begin(0); - uint64_t num_dictionary_entries; + uint64_t num_dictionary_entries{0}; file_reader.read_numeric_value(num_dictionary_entries, false); file_reader.seek_from_begin(dictionary_file_reader_pos); return num_dictionary_entries; @@ -39,7 +16,7 @@ uint64_t read_segment_index_header(FileReader& file_reader) { // Read segment index header auto segment_index_file_reader_pos = file_reader.get_pos(); file_reader.seek_from_begin(0); - uint64_t num_segments; + uint64_t num_segments{0}; file_reader.read_numeric_value(num_segments, false); file_reader.seek_from_begin(segment_index_file_reader_pos); return num_segments; diff --git a/components/core/src/clp/dictionary_utils.hpp b/components/core/src/clp/dictionary_utils.hpp index 42012964f..14509a4e9 100644 --- a/components/core/src/clp/dictionary_utils.hpp +++ b/components/core/src/clp/dictionary_utils.hpp @@ -1,22 +1,10 @@ #ifndef CLP_DICTIONARY_UTILS_HPP #define CLP_DICTIONARY_UTILS_HPP -#include - #include "FileReader.hpp" #include "streaming_compression/Decompressor.hpp" namespace clp { -void open_dictionary_for_reading( - std::string const& dictionary_path, - std::string const& segment_index_path, - size_t decompressor_file_read_buffer_capacity, - FileReader& dictionary_file_reader, - streaming_compression::Decompressor& dictionary_decompressor, - FileReader& segment_index_file_reader, - streaming_compression::Decompressor& segment_index_decompressor -); - uint64_t read_dictionary_header(FileReader& file_reader); uint64_t read_segment_index_header(FileReader& file_reader); diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp new file mode 100644 index 000000000..a8a8cf617 --- /dev/null +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp @@ -0,0 +1,455 @@ +#include "KeyValuePairLogEvent.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../ir/EncodedTextAst.hpp" +#include "../time_types.hpp" +#include "SchemaTree.hpp" +#include "Value.hpp" + +using clp::ir::EightByteEncodedTextAst; +using clp::ir::FourByteEncodedTextAst; +using std::string; +using std::vector; + +namespace clp::ffi { +namespace { +/** + * Concept for a function to handle a JSON exception. + * @tparam Func + */ +template +concept JsonExceptionHandlerConcept = std::is_invocable_v; + +/** + * Helper class for `KeyValuePairLogEvent::serialize_to_json`, used to: + * - iterate over the children of a non-leaf schema tree node, so long as those children are in the + * subtree defined by the `KeyValuePairLogEvent`. + * - group a non-leaf schema tree node with the JSON object that it's being serialized into. + * - add the node's corresponding JSON object to its parent's corresponding JSON object (or if the + * node is the root, replace the parent JSON object) when this class is destructed. + * @tparam JsonExceptionHandler Type of handler for any `nlohmann::json::exception` that occurs + * during destruction. + */ +template +class JsonSerializationIterator { +public: + // Constructor + JsonSerializationIterator( + SchemaTree::Node const* schema_tree_node, + vector const& schema_subtree_bitmap, + nlohmann::json::object_t* parent_json_obj, + JsonExceptionHandler json_exception_callback + ) + : m_schema_tree_node{schema_tree_node}, + m_parent_json_obj{parent_json_obj}, + m_json_exception_callback{json_exception_callback} { + for (auto const child_id : schema_tree_node->get_children_ids()) { + if (schema_subtree_bitmap[child_id]) { + m_child_schema_tree_nodes.push_back(child_id); + } + } + m_child_schema_tree_node_it = m_child_schema_tree_nodes.cbegin(); + } + + // Delete copy/move constructor and assignment + JsonSerializationIterator(JsonSerializationIterator const&) = delete; + JsonSerializationIterator(JsonSerializationIterator&&) = delete; + auto operator=(JsonSerializationIterator const&) -> JsonSerializationIterator& = delete; + auto operator=(JsonSerializationIterator&&) -> JsonSerializationIterator& = delete; + + // Destructor + ~JsonSerializationIterator() { + try { + // If the current node is the root, then replace the `parent` with this node's JSON + // object. Otherwise, add this node's JSON object as a child of the parent JSON object. + if (m_schema_tree_node->is_root()) { + *m_parent_json_obj = std::move(m_json_obj); + } else { + m_parent_json_obj->emplace( + string{m_schema_tree_node->get_key_name()}, + std::move(m_json_obj) + ); + } + } catch (nlohmann::json::exception const& ex) { + m_json_exception_callback(ex); + } + } + + /** + * @return Whether there are more child schema tree nodes to traverse. + */ + [[nodiscard]] auto has_next_child_schema_tree_node() const -> bool { + return m_child_schema_tree_node_it != m_child_schema_tree_nodes.end(); + } + + /** + * Gets the next child schema tree node and advances the iterator. + * @return The next child schema tree node. + */ + [[nodiscard]] auto get_next_child_schema_tree_node() -> SchemaTree::Node::id_t { + return *(m_child_schema_tree_node_it++); + } + + [[nodiscard]] auto get_json_obj() -> nlohmann::json::object_t& { return m_json_obj; } + +private: + SchemaTree::Node const* m_schema_tree_node; + vector m_child_schema_tree_nodes; + vector::const_iterator m_child_schema_tree_node_it; + nlohmann::json::object_t* m_parent_json_obj; + nlohmann::json::object_t m_json_obj; + JsonExceptionHandler m_json_exception_callback; +}; + +/** + * @param type + * @param value + * @return Whether the given schema tree node type matches the given value's type. + */ +[[nodiscard]] auto +node_type_matches_value_type(SchemaTree::Node::Type type, Value const& value) -> bool; + +/** + * Validates whether the given node-ID value pairs are leaf nodes in the `SchemaTree` forming a + * sub-tree of their own. + * @param schema_tree + * @param node_id_value_pairs + * @return success if the inputs are valid, or an error code indicating the failure: + * - std::errc::operation_not_permitted if a node ID doesn't represent a valid node in the + * schema tree, or a non-leaf node ID is paired with a value. + * - std::errc::protocol_error if the schema tree node type doesn't match the value's type. + * - std::errc::protocol_not_supported if the same key appears more than once under a parent + * node. + */ +[[nodiscard]] auto validate_node_id_value_pairs( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> std::errc; + +/** + * @param schema_tree + * @param node_id + * @param node_id_value_pairs + * @return Whether the given node is a leaf node in the sub-tree of the `SchemaTree` defined by + * `node_id_value_pairs`. A node is considered a leaf if none of its descendants appear in + * `node_id_value_pairs`. + */ +[[nodiscard]] auto is_leaf_node( + SchemaTree const& schema_tree, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> bool; + +/** + * Inserts the given key-value pair into the JSON object (map). + * @param node The schema tree node of the key to insert. + * @param optional_val The value to insert. + * @param json_obj The JSON object to insert the kv-pair into. + * @return Whether the insertion was successful. + */ +[[nodiscard]] auto insert_kv_pair_into_json_obj( + SchemaTree::Node const& node, + std::optional const& optional_val, + nlohmann::json::object_t& json_obj +) -> bool; + +/** + * Decodes a value as an `EncodedTextAst` according to the encoding type. + * NOTE: This function assumes that `val` is either a `FourByteEncodedTextAst` or + * `EightByteEncodedTextAst`. + * @param val + * @return Same as `EncodedTextAst::decode_and_unparse`. + */ +[[nodiscard]] auto decode_as_encoded_text_ast(Value const& val) -> std::optional; + +auto node_type_matches_value_type(SchemaTree::Node::Type type, Value const& value) -> bool { + switch (type) { + case SchemaTree::Node::Type::Obj: + return value.is_null(); + case SchemaTree::Node::Type::Int: + return value.is(); + case SchemaTree::Node::Type::Float: + return value.is(); + case SchemaTree::Node::Type::Bool: + return value.is(); + case SchemaTree::Node::Type::UnstructuredArray: + return value.is() || value.is(); + case SchemaTree::Node::Type::Str: + return value.is() || value.is() + || value.is(); + default: + return false; + } +} + +auto validate_node_id_value_pairs( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> std::errc { + try { + std::unordered_map> + parent_node_id_to_key_names; + for (auto const& [node_id, value] : node_id_value_pairs) { + auto const& node{schema_tree.get_node(node_id)}; + if (node.is_root()) { + return std::errc::operation_not_permitted; + } + + auto const node_type{node.get_type()}; + if (false == value.has_value()) { + // Value is an empty object (`{}`, which is not the same as `null`) + if (SchemaTree::Node::Type::Obj != node_type) { + return std::errc::protocol_error; + } + } else if (false == node_type_matches_value_type(node_type, value.value())) { + return std::errc::protocol_error; + } + + if (SchemaTree::Node::Type::Obj == node_type + && false == is_leaf_node(schema_tree, node_id, node_id_value_pairs)) + { + // The node's value is `null` or `{}` but its descendants appear in + // `node_id_value_pairs`. + return std::errc::operation_not_permitted; + } + + // We checked that the node isn't the root above, so we can query the underlying ID + // safely without a repeated check. + auto const parent_node_id{node.get_parent_id_unsafe()}; + auto const key_name{node.get_key_name()}; + if (parent_node_id_to_key_names.contains(parent_node_id)) { + auto const [it, new_key_inserted]{ + parent_node_id_to_key_names.at(parent_node_id).emplace(key_name) + }; + if (false == new_key_inserted) { + // The key is duplicated under the same parent + return std::errc::protocol_not_supported; + } + } else { + parent_node_id_to_key_names.emplace(parent_node_id, std::unordered_set{key_name}); + } + } + } catch (SchemaTree::OperationFailed const& ex) { + return std::errc::operation_not_permitted; + } + return std::errc{}; +} + +auto is_leaf_node( + SchemaTree const& schema_tree, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> bool { + vector dfs_stack; + dfs_stack.reserve(schema_tree.get_size()); + dfs_stack.push_back(node_id); + while (false == dfs_stack.empty()) { + auto const curr_node_id{dfs_stack.back()}; + dfs_stack.pop_back(); + for (auto const child_node_id : schema_tree.get_node(curr_node_id).get_children_ids()) { + if (node_id_value_pairs.contains(child_node_id)) { + return false; + } + dfs_stack.push_back(child_node_id); + } + } + return true; +} + +auto insert_kv_pair_into_json_obj( + SchemaTree::Node const& node, + std::optional const& optional_val, + nlohmann::json::object_t& json_obj +) -> bool { + string const key_name{node.get_key_name()}; + auto const type{node.get_type()}; + if (false == optional_val.has_value()) { + json_obj.emplace(key_name, nlohmann::json::object()); + return true; + } + + try { + auto const& val{optional_val.value()}; + switch (type) { + case SchemaTree::Node::Type::Int: + json_obj.emplace(key_name, val.get_immutable_view()); + break; + case SchemaTree::Node::Type::Float: + json_obj.emplace(key_name, val.get_immutable_view()); + break; + case SchemaTree::Node::Type::Bool: + json_obj.emplace(key_name, val.get_immutable_view()); + break; + case SchemaTree::Node::Type::Str: + if (val.is()) { + json_obj.emplace(key_name, string{val.get_immutable_view()}); + } else { + auto const decoded_result{decode_as_encoded_text_ast(val)}; + if (false == decoded_result.has_value()) { + return false; + } + json_obj.emplace(key_name, decoded_result.value()); + } + break; + case SchemaTree::Node::Type::UnstructuredArray: { + auto const decoded_result{decode_as_encoded_text_ast(val)}; + if (false == decoded_result.has_value()) { + return false; + } + json_obj.emplace(key_name, nlohmann::json::parse(decoded_result.value())); + break; + } + case SchemaTree::Node::Type::Obj: + json_obj.emplace(key_name, nullptr); + break; + default: + return false; + } + } catch (nlohmann::json::exception const& ex) { + return false; + } catch (Value::OperationFailed const& ex) { + return false; + } + + return true; +} + +auto decode_as_encoded_text_ast(Value const& val) -> std::optional { + return val.is() + ? val.get_immutable_view().decode_and_unparse() + : val.get_immutable_view().decode_and_unparse(); +} +} // namespace + +auto KeyValuePairLogEvent::create( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset +) -> OUTCOME_V2_NAMESPACE::std_result { + if (auto const ret_val{validate_node_id_value_pairs(*schema_tree, node_id_value_pairs)}; + std::errc{} != ret_val) + { + return ret_val; + } + return KeyValuePairLogEvent{std::move(schema_tree), std::move(node_id_value_pairs), utc_offset}; +} + +auto KeyValuePairLogEvent::get_schema_subtree_bitmap( +) const -> OUTCOME_V2_NAMESPACE::std_result> { + auto schema_subtree_bitmap{vector(m_schema_tree->get_size(), false)}; + for (auto const& [node_id, val] : m_node_id_value_pairs) { + if (node_id >= schema_subtree_bitmap.size()) { + return std::errc::result_out_of_range; + } + schema_subtree_bitmap[node_id] = true; + + // Iteratively mark the parents as true + auto optional_parent_id{m_schema_tree->get_node(node_id).get_parent_id()}; + while (true) { + // Ideally, we'd use this if statement as the loop condition, but clang-tidy will + // complain about an unchecked `optional` access. + if (false == optional_parent_id.has_value()) { + // Reached the root + break; + } + auto const parent_id{optional_parent_id.value()}; + if (schema_subtree_bitmap[parent_id]) { + // Parent already set by other child + break; + } + schema_subtree_bitmap[parent_id] = true; + optional_parent_id = m_schema_tree->get_node(parent_id).get_parent_id(); + } + } + return schema_subtree_bitmap; +} + +auto KeyValuePairLogEvent::serialize_to_json( +) const -> OUTCOME_V2_NAMESPACE::std_result { + if (m_node_id_value_pairs.empty()) { + return nlohmann::json::object(); + } + + bool json_exception_captured{false}; + auto json_exception_handler = [&]([[maybe_unused]] nlohmann::json::exception const& ex + ) -> void { json_exception_captured = true; }; + using DfsIterator = JsonSerializationIterator; + + // NOTE: We use a `std::stack` (which uses `std::deque` as the underlying container) instead of + // a `std::vector` to avoid implementing move semantics for `DfsIterator` (required when the + // vector grows). + std::stack dfs_stack; + + auto const schema_subtree_bitmap_ret{get_schema_subtree_bitmap()}; + if (schema_subtree_bitmap_ret.has_error()) { + return schema_subtree_bitmap_ret.error(); + } + auto const& schema_subtree_bitmap{schema_subtree_bitmap_ret.value()}; + + // Traverse the schema tree in DFS order, but only traverse the nodes that are set in + // `schema_subtree_bitmap`. + // + // On the way down: + // - for each non-leaf node, create a `nlohmann::json::object_t`; + // - for each leaf node, insert the key-value pair into the parent `nlohmann::json::object_t`. + // + // On the way up, add the current node's `nlohmann::json::object_t` to the parent's + // `nlohmann::json::object_t`. + auto const& root_schema_tree_node{m_schema_tree->get_root()}; + auto root_json_obj = nlohmann::json::object_t(); + + dfs_stack.emplace( + &root_schema_tree_node, + schema_subtree_bitmap, + &root_json_obj, + json_exception_handler + ); + while (false == dfs_stack.empty() && false == json_exception_captured) { + auto& top{dfs_stack.top()}; + if (false == top.has_next_child_schema_tree_node()) { + dfs_stack.pop(); + continue; + } + auto const child_schema_tree_node_id{top.get_next_child_schema_tree_node()}; + auto const& child_schema_tree_node{m_schema_tree->get_node(child_schema_tree_node_id)}; + if (m_node_id_value_pairs.contains(child_schema_tree_node_id)) { + // Handle leaf node + if (false + == insert_kv_pair_into_json_obj( + child_schema_tree_node, + m_node_id_value_pairs.at(child_schema_tree_node_id), + top.get_json_obj() + )) + { + return std::errc::protocol_error; + } + } else { + dfs_stack.emplace( + &child_schema_tree_node, + schema_subtree_bitmap, + &top.get_json_obj(), + json_exception_handler + ); + } + } + + if (json_exception_captured) { + return std::errc::protocol_error; + } + + return root_json_obj; +} +} // namespace clp::ffi diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp new file mode 100644 index 000000000..f6334d378 --- /dev/null +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp @@ -0,0 +1,105 @@ +#ifndef CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP +#define CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP + +#include +#include +#include +#include +#include + +#include +#include + +#include "../time_types.hpp" +#include "SchemaTree.hpp" +#include "Value.hpp" + +namespace clp::ffi { +/** + * A log event containing key-value pairs. Each event contains: + * - A collection of node-ID & value pairs, where each pair represents a leaf `SchemaTreeNode` in + * the `SchemaTree`. + * - A reference to the `SchemaTree` + * - The UTC offset of the current log event + */ +class KeyValuePairLogEvent { +public: + // Types + using NodeIdValuePairs = std::unordered_map>; + + // Factory functions + /** + * @param schema_tree + * @param node_id_value_pairs + * @param utc_offset + * @return A result containing the key-value pair log event or an error code indicating the + * failure. See `validate_node_id_value_pairs` for the possible error codes. + */ + [[nodiscard]] static auto create( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset + ) -> OUTCOME_V2_NAMESPACE::std_result; + + // Disable copy constructor and assignment operator + KeyValuePairLogEvent(KeyValuePairLogEvent const&) = delete; + auto operator=(KeyValuePairLogEvent const&) -> KeyValuePairLogEvent& = delete; + + // Default move constructor and assignment operator + KeyValuePairLogEvent(KeyValuePairLogEvent&&) = default; + auto operator=(KeyValuePairLogEvent&&) -> KeyValuePairLogEvent& = default; + + // Destructor + ~KeyValuePairLogEvent() = default; + + // Methods + [[nodiscard]] auto get_schema_tree() const -> SchemaTree const& { return *m_schema_tree; } + + [[nodiscard]] auto get_node_id_value_pairs() const -> NodeIdValuePairs const& { + return m_node_id_value_pairs; + } + + [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } + + /** + * @return A result containing a bitmap where every bit corresponds to the ID of a node in the + * schema tree, and the set bits correspond to the nodes in the subtree defined by all paths + * from the root node to the nodes in `node_id_value_pairs`; or an error code indicating a + * failure: + * - std::errc::result_out_of_range if a node ID in `node_id_value_pairs` doesn't exist in the + * schema tree. + */ + [[nodiscard]] auto get_schema_subtree_bitmap( + ) const -> OUTCOME_V2_NAMESPACE::std_result>; + + /** + * Serializes the log event into a `nlohmann::json` object. + * @return A result containing the serialized JSON object or an error code indicating the + * failure: + * - std::errc::protocol_error if a value in the log event couldn't be decoded or it couldn't be + * inserted into a JSON object. + * - std::errc::result_out_of_range if a node ID in the log event doesn't exist in the schema + * tree. + */ + [[nodiscard]] auto serialize_to_json( + ) const -> OUTCOME_V2_NAMESPACE::std_result; + +private: + // Constructor + KeyValuePairLogEvent( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset + ) + : m_schema_tree{std::move(schema_tree)}, + m_node_id_value_pairs{std::move(node_id_value_pairs)}, + m_utc_offset{utc_offset} {} + + // Variables + std::shared_ptr m_schema_tree; + NodeIdValuePairs m_node_id_value_pairs; + UtcOffset m_utc_offset{0}; +}; +} // namespace clp::ffi + +#endif // CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP diff --git a/components/core/src/clp/ffi/SchemaTree.cpp b/components/core/src/clp/ffi/SchemaTree.cpp index 5cc45ac65..08c1c4169 100644 --- a/components/core/src/clp/ffi/SchemaTree.cpp +++ b/components/core/src/clp/ffi/SchemaTree.cpp @@ -5,10 +5,9 @@ #include #include "../ErrorCode.hpp" -#include "SchemaTreeNode.hpp" namespace clp::ffi { -auto SchemaTree::get_node(SchemaTreeNode::id_t id) const -> SchemaTreeNode const& { +auto SchemaTree::get_node(Node::id_t id) const -> Node const& { if (m_tree_nodes.size() <= static_cast(id)) { throw OperationFailed( ErrorCode_OutOfBounds, @@ -20,13 +19,12 @@ auto SchemaTree::get_node(SchemaTreeNode::id_t id) const -> SchemaTreeNode const return m_tree_nodes[id]; } -auto SchemaTree::try_get_node_id(NodeLocator const& locator -) const -> std::optional { +auto SchemaTree::try_get_node_id(NodeLocator const& locator) const -> std::optional { auto const parent_id{static_cast(locator.get_parent_id())}; if (m_tree_nodes.size() <= parent_id) { - return false; + return std::nullopt; } - std::optional node_id; + std::optional node_id; for (auto const child_id : m_tree_nodes[parent_id].get_children_ids()) { auto const& node{m_tree_nodes[child_id]}; if (node.get_key_name() == locator.get_key_name() && node.get_type() == locator.get_type()) @@ -38,19 +36,14 @@ auto SchemaTree::try_get_node_id(NodeLocator const& locator return node_id; } -auto SchemaTree::insert_node(NodeLocator const& locator) -> SchemaTreeNode::id_t { +auto SchemaTree::insert_node(NodeLocator const& locator) -> Node::id_t { if (try_get_node_id(locator).has_value()) { throw OperationFailed(ErrorCode_Failure, __FILE__, __LINE__, "Node already exists."); } - auto const node_id{static_cast(m_tree_nodes.size())}; - m_tree_nodes.emplace_back( - node_id, - locator.get_parent_id(), - locator.get_key_name(), - locator.get_type() - ); + auto const node_id{static_cast(m_tree_nodes.size())}; + m_tree_nodes.emplace_back(Node::create(node_id, locator)); auto& parent_node{m_tree_nodes[locator.get_parent_id()]}; - if (SchemaTreeNode::Type::Obj != parent_node.get_type()) { + if (Node::Type::Obj != parent_node.get_type()) { throw OperationFailed( ErrorCode_Failure, __FILE__, @@ -68,7 +61,10 @@ auto SchemaTree::revert() -> void { } while (m_tree_nodes.size() != m_snapshot_size) { auto const& node{m_tree_nodes.back()}; - m_tree_nodes[node.get_parent_id()].remove_last_appended_child(); + auto const optional_parent_id{node.get_parent_id()}; + if (optional_parent_id.has_value()) { + m_tree_nodes[optional_parent_id.value()].remove_last_appended_child(); + } m_tree_nodes.pop_back(); } m_snapshot_size.reset(); diff --git a/components/core/src/clp/ffi/SchemaTree.hpp b/components/core/src/clp/ffi/SchemaTree.hpp index 6683103c5..46494fa71 100644 --- a/components/core/src/clp/ffi/SchemaTree.hpp +++ b/components/core/src/clp/ffi/SchemaTree.hpp @@ -2,6 +2,7 @@ #define CLP_FFI_SCHEMATREE_HPP #include +#include #include #include #include @@ -10,7 +11,6 @@ #include "../ErrorCode.hpp" #include "../TraceableException.hpp" -#include "SchemaTreeNode.hpp" namespace clp::ffi { /** @@ -91,6 +91,122 @@ class SchemaTree { std::string m_message; }; + // Forward declared types + class Node; + class NodeLocator; + + /** + * A node in clp::ffi::SchemaTree. It stores the node's key name, type, parent's ID, and the IDs + * of all its children. + */ + class Node { + public: + // Types + using id_t = uint32_t; + + /** + * Enum defining the possible node types. + */ + enum class Type : uint8_t { + Int = 0, + Float, + Bool, + Str, + UnstructuredArray, + Obj + }; + + // Disable copy constructor/assignment operator + Node(Node const&) = delete; + auto operator=(Node const&) -> Node& = delete; + + // Define default move constructor/assignment operator + Node(Node&&) = default; + auto operator=(Node&&) -> Node& = default; + + // Destructor + ~Node() = default; + + // Methods + [[nodiscard]] auto get_id() const -> id_t { return m_id; } + + [[nodiscard]] auto is_root() const -> bool { return false == m_parent_id.has_value(); } + + /** + * @return The ID of the parent node in the schema tree, if the node is not the root. + * @return std::nullopt if the node is the root. + */ + [[nodiscard]] auto get_parent_id() const -> std::optional { return m_parent_id; } + + /** + * Gets the parent ID without checking if it's `std::nullopt`. + * NOTE: This method should only be used if the caller has checked the node is not the root. + * @return The ID of the parent node in the schema tree. + */ + [[nodiscard]] auto get_parent_id_unsafe() const -> id_t { + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + return m_parent_id.value(); + } + + [[nodiscard]] auto get_key_name() const -> std::string_view { return m_key_name; } + + [[nodiscard]] auto get_type() const -> Type { return m_type; } + + [[nodiscard]] auto get_children_ids() const -> std::vector const& { + return m_children_ids; + } + + /** + * Appends a child using its node ID. + * NOTE: This method doesn't check if a child with the given ID already exists. + * @param child_id The child node's ID. + */ + auto append_new_child(id_t child_id) -> void { m_children_ids.push_back(child_id); } + + /** + * Removes the last appended child ID (if any). + */ + auto remove_last_appended_child() -> void { + if (m_children_ids.empty()) { + return; + } + m_children_ids.pop_back(); + } + + private: + friend SchemaTree; + + // Factory functions + /** + * Creates a non-root tree node. + * @param id + * @param locator + */ + [[nodiscard]] static auto create(id_t id, NodeLocator const& locator) -> Node { + return {id, locator.get_parent_id(), locator.get_key_name(), locator.get_type()}; + } + + /** + * Creates a root node. + */ + [[nodiscard]] static auto create_root() -> Node { + return {cRootId, std::nullopt, {}, Type::Obj}; + } + + // Constructors + Node(id_t id, std::optional parent_id, std::string_view key_name, Type type) + : m_id{id}, + m_parent_id{parent_id}, + m_key_name{key_name.begin(), key_name.end()}, + m_type{type} {} + + id_t m_id; + std::optional m_parent_id; + std::vector m_children_ids; + std::string m_key_name; + Type m_type; + }; + /** * A triple---parent ID, key name, and node type---that uniquely identifies a node. * NOTE: We use the term "Locator" to avoid terms like "Key" or "Identifier" that are already in @@ -98,32 +214,28 @@ class SchemaTree { */ class NodeLocator { public: - NodeLocator( - SchemaTreeNode::id_t parent_id, - std::string_view key_name, - SchemaTreeNode::Type type - ) + NodeLocator(Node::id_t parent_id, std::string_view key_name, Node::Type type) : m_parent_id{parent_id}, m_key_name{key_name}, m_type{type} {} - [[nodiscard]] auto get_parent_id() const -> SchemaTreeNode::id_t { return m_parent_id; } + [[nodiscard]] auto get_parent_id() const -> Node::id_t { return m_parent_id; } [[nodiscard]] auto get_key_name() const -> std::string_view { return m_key_name; } - [[nodiscard]] auto get_type() const -> SchemaTreeNode::Type { return m_type; } + [[nodiscard]] auto get_type() const -> Node::Type { return m_type; } private: - SchemaTreeNode::id_t m_parent_id; + Node::id_t m_parent_id; std::string_view m_key_name; - SchemaTreeNode::Type m_type; + Node::Type m_type; }; // Constants - static constexpr SchemaTreeNode::id_t cRootId{0}; + static constexpr Node::id_t cRootId{0}; // Constructors - SchemaTree() { m_tree_nodes.emplace_back(cRootId, cRootId, "", SchemaTreeNode::Type::Obj); } + SchemaTree() { m_tree_nodes.emplace_back(Node::create_root()); } // Disable copy constructor/assignment operator SchemaTree(SchemaTree const&) = delete; @@ -139,12 +251,14 @@ class SchemaTree { // Methods [[nodiscard]] auto get_size() const -> size_t { return m_tree_nodes.size(); } + [[nodiscard]] auto get_root() const -> Node const& { return m_tree_nodes[cRootId]; } + /** * @param id * @return The node with the given ID. * @throw OperationFailed if a node with the given ID doesn't exist in the tree. */ - [[nodiscard]] auto get_node(SchemaTreeNode::id_t id) const -> SchemaTreeNode const&; + [[nodiscard]] auto get_node(Node::id_t id) const -> Node const&; /** * Tries to get the ID of a node corresponding to the given locator, if the node exists. @@ -153,7 +267,7 @@ class SchemaTree { * @return std::nullopt otherwise. */ [[nodiscard]] auto try_get_node_id(NodeLocator const& locator - ) const -> std::optional; + ) const -> std::optional; /** * @param locator @@ -167,9 +281,11 @@ class SchemaTree { * Inserts a new node corresponding to the given locator. * @param locator * @return The ID of the inserted node. - * @throw OperationFailed if a node that corresponds to the given locator already exists. + * @throw OperationFailed if: + * - a node that corresponds to the given locator already exists. + * - the parent node identified by the locator is not an object. */ - [[maybe_unused]] auto insert_node(NodeLocator const& locator) -> SchemaTreeNode::id_t; + [[maybe_unused]] auto insert_node(NodeLocator const& locator) -> Node::id_t; /** * Takes a snapshot of the current schema tree (to allow recovery on failure). @@ -182,18 +298,10 @@ class SchemaTree { */ auto revert() -> void; - /** - * Resets the schema tree by removing all nodes except the root. - */ - auto reset() -> void { - m_snapshot_size.reset(); - m_tree_nodes.clear(); - m_tree_nodes.emplace_back(cRootId, cRootId, "", SchemaTreeNode::Type::Obj); - } - private: + // Variables std::optional m_snapshot_size; - std::vector m_tree_nodes; + std::vector m_tree_nodes; }; } // namespace clp::ffi #endif diff --git a/components/core/src/clp/ffi/SchemaTreeNode.hpp b/components/core/src/clp/ffi/SchemaTreeNode.hpp deleted file mode 100644 index 7bbe2a06a..000000000 --- a/components/core/src/clp/ffi/SchemaTreeNode.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef CLP_FFI_SCHEMATREENODE_HPP -#define CLP_FFI_SCHEMATREENODE_HPP - -#include -#include -#include -#include - -namespace clp::ffi { -/** - * A node in clp::ffi::SchemaTree. It stores the node's key name, type, parent's ID, and the IDs of - * all its children. - */ -class SchemaTreeNode { -public: - // Types - using id_t = uint32_t; - - /** - * Enum defining the possible node types. - */ - enum class Type : uint8_t { - Int = 0, - Float, - Bool, - Str, - UnstructuredArray, - Obj - }; - - // Constructors - SchemaTreeNode(id_t id, id_t parent_id, std::string_view key_name, Type type) - : m_id{id}, - m_parent_id{parent_id}, - m_key_name{key_name.begin(), key_name.end()}, - m_type{type} {} - - // Disable copy constructor/assignment operator - SchemaTreeNode(SchemaTreeNode const&) = delete; - auto operator=(SchemaTreeNode const&) -> SchemaTreeNode& = delete; - - // Define default move constructor/assignment operator - SchemaTreeNode(SchemaTreeNode&&) = default; - auto operator=(SchemaTreeNode&&) -> SchemaTreeNode& = default; - - // Destructor - ~SchemaTreeNode() = default; - - // Methods - [[nodiscard]] auto get_id() const -> id_t { return m_id; } - - [[nodiscard]] auto get_parent_id() const -> id_t { return m_parent_id; } - - [[nodiscard]] auto get_key_name() const -> std::string_view { return m_key_name; } - - [[nodiscard]] auto get_type() const -> Type { return m_type; } - - [[nodiscard]] auto get_children_ids() const -> std::vector const& { - return m_children_ids; - } - - /** - * Appends a child using its node ID. - * NOTE: This method doesn't check if a child with the given ID already exists. - * @param child_id The child node's ID. - */ - auto append_new_child(id_t child_id) -> void { m_children_ids.push_back(child_id); } - - /** - * Removes the last appended child ID (if any). - */ - auto remove_last_appended_child() -> void { - if (m_children_ids.empty()) { - return; - } - m_children_ids.pop_back(); - } - -private: - id_t m_id; - id_t m_parent_id; - std::vector m_children_ids; - std::string m_key_name; - Type m_type; -}; -} // namespace clp::ffi - -#endif diff --git a/components/core/src/clp/ffi/Value.hpp b/components/core/src/clp/ffi/Value.hpp new file mode 100644 index 000000000..961d12366 --- /dev/null +++ b/components/core/src/clp/ffi/Value.hpp @@ -0,0 +1,193 @@ +#ifndef CLP_FFI_VALUE_HPP +#define CLP_FFI_VALUE_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "../ErrorCode.hpp" +#include "../ir/EncodedTextAst.hpp" +#include "../TraceableException.hpp" +#include "../type_utils.hpp" + +// NOTE: In this file, "primitive" doesn't refer to a C++ fundamental type (e.g. int) but instead +// refers to a value in a kv-pair that has no children (i.e. not an object/array). + +namespace clp::ffi { +using value_int_t = int64_t; +using value_float_t = double; +using value_bool_t = bool; + +/** + * Tuple of all primitive value types. + */ +using PrimitiveValueTypes = std::tuple< + value_int_t, + value_float_t, + value_bool_t, + std::string, + clp::ir::EightByteEncodedTextAst, + clp::ir::FourByteEncodedTextAst>; + +/** + * Variant for all primitive value types. + */ +using PrimitiveValueVariant = tuple_to_variant::Type; + +/** + * Template to validate whether the given type is a primitive value type. + * @tparam T + */ +template +constexpr bool cIsPrimitiveValueType = is_in_type_tuple::value; + +/** + * Concept that defines primitive value types. + */ +template +concept PrimitiveValueType = cIsPrimitiveValueType; + +/** + * Concept that defines primitive value types that are also C++ fundamental types. + */ +template +concept FundamentalPrimitiveValueType = cIsPrimitiveValueType && std::is_fundamental_v; + +/** + * Concept that defines move-constructable primitive value types. + */ +template +concept MoveConstructablePrimitiveValueType + = cIsPrimitiveValueType && std::is_move_constructible_v + && (false == std::is_fundamental_v); + +/** + * Template struct that converts a given type into an immutable view type. By default, the immutable + * view type is the given type itself, meaning that the immutable view is a copy. Specialization is + * needed when the immutable view type is a const reference or some other types. + * @tparam T + */ +template +struct ImmutableViewTypeConverter { + using Type = T; +}; + +/** + * Specializes `std::string`'s immutable view type as a `std::string_view`. + */ +template <> +struct ImmutableViewTypeConverter { + using Type = std::string_view; +}; + +/** + * Specializes `clp::ir::EightByteEncodedTextAst`'s immutable view type as its const reference. + */ +template <> +struct ImmutableViewTypeConverter { + using Type = clp::ir::EightByteEncodedTextAst const&; +}; + +/** + * Specializes `clp::ir::FourByteEncodedTextAst`'s immutable view type as its const reference. + */ +template <> +struct ImmutableViewTypeConverter { + using Type = clp::ir::FourByteEncodedTextAst const&; +}; + +/** + * Template alias to the underlying typename of `ImmutableViewTypeConverter`. + * @tparam T + */ +template +using ImmutableViewType = typename ImmutableViewTypeConverter::Type; + +class Value { +public: + // Types + class OperationFailed : public TraceableException { + public: + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string message + ) + : TraceableException{error_code, filename, line_number}, + m_message{std::move(message)} {} + + [[nodiscard]] auto what() const noexcept -> char const* override { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + // Constructors + Value() = default; + + /** + * Constructs a `Value` by moving the given primitive value. + * @tparam T The type of the value, which must be an rvalue to a move-constructable primitive + * value type. + * @param value + */ + template + explicit Value(T&& value) : m_value{std::forward(value)} { + static_assert(std::is_rvalue_reference_v); + } + + /** + * @tparam T The type of the given value, which must be a fundamental primitive value type. + * @param value + */ + template + explicit Value(T value) : m_value{value} {} + + // Methods + /** + * @tparam T + * @return Whether the underlying value is the given type. + */ + template + [[nodiscard]] auto is() const -> bool { + return std::holds_alternative(m_value); + } + + /** + * @tparam T + * @return An immutable view of the underlying value if its type matches the given type. + * @throw OperationFailed if the given type doesn't match the underlying value's type. + */ + template + [[nodiscard]] auto get_immutable_view() const -> ImmutableViewType { + if (false == is()) { + throw OperationFailed( + clp::ErrorCode_BadParam, + __FILE__, + __LINE__, + "The underlying value does not match the given type." + ); + } + return std::get(m_value); + } + + /** + * @return Whether the underlying value is null. + */ + [[nodiscard]] auto is_null() const -> bool { + return std::holds_alternative(m_value); + } + +private: + PrimitiveValueVariant m_value{std::monostate{}}; +}; +} // namespace clp::ffi + +#endif // CLP_FFI_VALUE_HPP diff --git a/components/core/src/clp/ffi/ir_stream/Deserializer.hpp b/components/core/src/clp/ffi/ir_stream/Deserializer.hpp new file mode 100644 index 000000000..3418a39ae --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/Deserializer.hpp @@ -0,0 +1,261 @@ +#ifndef CLP_FFI_IR_STREAM_DESERIALIZER_HPP +#define CLP_FFI_IR_STREAM_DESERIALIZER_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../ReaderInterface.hpp" +#include "../../time_types.hpp" +#include "../SchemaTree.hpp" +#include "decoding_methods.hpp" +#include "ir_unit_deserialization_methods.hpp" +#include "IrUnitHandlerInterface.hpp" +#include "IrUnitType.hpp" +#include "protocol_constants.hpp" +#include "utils.hpp" + +namespace clp::ffi::ir_stream { +/** + * A deserializer for reading IR units from a CLP kv-pair IR stream. An IR unit handler should be + * provided to perform user-defined operations on each deserialized IR unit. + * + * NOTE: This class is designed only to provide deserialization functionalities. Callers are + * responsible for maintaining a `ReaderInterface` to input IR bytes from an I/O stream. + * + * @tparam IrUnitHandler + */ +template +requires(std::move_constructible) +class Deserializer { +public: + // Factory function + /** + * Creates a deserializer by reading the stream's preamble from the given reader. + * @param reader + * @param ir_unit_handler + * @return A result containing the deserializer or an error code indicating the failure: + * - std::errc::result_out_of_range if the IR stream is truncated + * - std::errc::protocol_error if the IR stream is corrupted + * - std::errc::protocol_not_supported if the IR stream contains an unsupported metadata format + * or uses an unsupported version + */ + [[nodiscard]] static auto create(ReaderInterface& reader, IrUnitHandler ir_unit_handler) + -> OUTCOME_V2_NAMESPACE::std_result; + + // Delete copy constructor and assignment + Deserializer(Deserializer const&) = delete; + auto operator=(Deserializer const&) -> Deserializer& = delete; + + // Define default move constructor and assignment + Deserializer(Deserializer&&) = default; + auto operator=(Deserializer&&) -> Deserializer& = default; + + // Destructor + ~Deserializer() = default; + + // Methods + /** + * Deserializes the stream from the given reader up to and including the next log event IR unit. + * @param reader + * @return Forwards `deserialize_tag`s return values if no tag bytes can be read to determine + * the next IR unit type. + * @return std::errc::protocol_not_supported if the IR unit type is not supported. + * @return std::errc::operation_not_permitted if the deserializer already reached the end of + * stream by deserializing an end-of-stream IR unit in the previous calls. + * @return IRUnitType::LogEvent if a log event IR unit is deserialized, or an error code + * indicating the failure: + * - Forwards `deserialize_ir_unit_kv_pair_log_event`'s return values if it failed to + * deserialize and construct the log event. + * - Forwards `handle_log_event`'s return values from the user-defined IR unit handler on + * unit handling failure. + * @return IRUnitType::SchemaTreeNodeInsertion if a schema tree node insertion IR unit is + * deserialized, or an error code indicating the failure: + * - Forwards `deserialize_ir_unit_schema_tree_node_insertion`'s return values if it failed to + * deserialize and construct the schema tree node locator. + * - Forwards `handle_schema_tree_node_insertion`'s return values from the user-defined IR unit + * handler on unit handling failure. + * - std::errc::protocol_error if the deserialized schema tree node already exists in the schema + * tree. + * @return IRUnitType::UtcOffsetChange if a UTC offset change IR unit is deserialized, or an + * error code indicating the failure: + * - Forwards `deserialize_ir_unit_utc_offset_change`'s return values if it failed to + * deserialize the UTC offset. + * - Forwards `handle_utc_offset_change`'s return values from the user-defined IR unit handler + * on unit handling failure. + * @return IRUnitType::EndOfStream if an end-of-stream IR unit is deserialized, or an error code + * indicating the failure: + * - Forwards `handle_end_of_stream`'s return values from the user-defined IR unit handler on + * unit handling failure. + */ + [[nodiscard]] auto deserialize_next_ir_unit(ReaderInterface& reader + ) -> OUTCOME_V2_NAMESPACE::std_result; + + /** + * @return Whether the stream has completed. A stream is considered completed if an + * end-of-stream IR unit has already been deserialized. + */ + [[nodiscard]] auto is_stream_completed() const -> bool { return m_is_complete; } + + [[nodiscard]] auto get_ir_unit_handler() const -> IrUnitHandler const& { + return m_ir_unit_handler; + } + + [[nodiscard]] auto get_ir_unit_handler() -> IrUnitHandler& { return m_ir_unit_handler; } + +private: + // Constructor + Deserializer(IrUnitHandler ir_unit_handler) : m_ir_unit_handler{std::move(ir_unit_handler)} {} + + // Variables + std::shared_ptr m_schema_tree{std::make_shared()}; + UtcOffset m_utc_offset{0}; + IrUnitHandler m_ir_unit_handler; + bool m_is_complete{false}; +}; + +template +requires(std::move_constructible) +auto Deserializer::create(ReaderInterface& reader, IrUnitHandler ir_unit_handler) + -> OUTCOME_V2_NAMESPACE::std_result { + bool is_four_byte_encoded{}; + if (auto const err{get_encoding_type(reader, is_four_byte_encoded)}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + + std::vector metadata; + encoded_tag_t metadata_type{}; + if (auto const err{deserialize_preamble(reader, metadata_type, metadata)}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + + if (cProtocol::Metadata::EncodingJson != metadata_type) { + return std::errc::protocol_not_supported; + } + + auto metadata_json = nlohmann::json::parse(metadata, nullptr, false); + if (metadata_json.is_discarded()) { + return std::errc::protocol_error; + } + auto const version_iter{metadata_json.find(cProtocol::Metadata::VersionKey)}; + if (metadata_json.end() == version_iter || false == version_iter->is_string()) { + return std::errc::protocol_error; + } + auto const version = version_iter->get_ref(); + if (ffi::ir_stream::IRProtocolErrorCode::Supported + != ffi::ir_stream::validate_protocol_version(version)) + { + return std::errc::protocol_not_supported; + } + + return Deserializer{std::move(ir_unit_handler)}; +} + +template +requires(std::move_constructible) +auto Deserializer::deserialize_next_ir_unit(ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result { + if (is_stream_completed()) { + return std::errc::operation_not_permitted; + } + + encoded_tag_t tag{}; + if (auto const err{deserialize_tag(reader, tag)}; IRErrorCode::IRErrorCode_Success != err) { + return ir_error_code_to_errc(err); + } + + auto const optional_ir_unit_type{get_ir_unit_type_from_tag(tag)}; + if (false == optional_ir_unit_type.has_value()) { + return std::errc::protocol_not_supported; + } + + auto const ir_unit_type{optional_ir_unit_type.value()}; + switch (ir_unit_type) { + case IrUnitType::LogEvent: { + auto result{ + deserialize_ir_unit_kv_pair_log_event(reader, tag, m_schema_tree, m_utc_offset) + }; + if (result.has_error()) { + return result.error(); + } + + if (auto const err{m_ir_unit_handler.handle_log_event(std::move(result.value()))}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + break; + } + + case IrUnitType::SchemaTreeNodeInsertion: { + std::string key_name; + auto const result{deserialize_ir_unit_schema_tree_node_insertion(reader, tag, key_name) + }; + if (result.has_error()) { + return result.error(); + } + + auto const node_locator{result.value()}; + if (m_schema_tree->has_node(node_locator)) { + return std::errc::protocol_error; + } + + if (auto const err{m_ir_unit_handler.handle_schema_tree_node_insertion(node_locator)}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + + std::ignore = m_schema_tree->insert_node(node_locator); + break; + } + + case IrUnitType::UtcOffsetChange: { + auto const result{deserialize_ir_unit_utc_offset_change(reader)}; + if (result.has_error()) { + return result.error(); + } + + auto const new_utc_offset{result.value()}; + if (auto const err{ + m_ir_unit_handler.handle_utc_offset_change(m_utc_offset, new_utc_offset) + }; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + + m_utc_offset = new_utc_offset; + break; + } + + case IrUnitType::EndOfStream: { + if (auto const err{m_ir_unit_handler.handle_end_of_stream()}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + m_is_complete = true; + break; + } + + default: + return std::errc::protocol_not_supported; + } + + return ir_unit_type; +} +} // namespace clp::ffi::ir_stream + +#endif // CLP_FFI_IR_STREAM_DESERIALIZER_HPP diff --git a/components/core/src/clp/ffi/ir_stream/IrUnitHandlerInterface.hpp b/components/core/src/clp/ffi/ir_stream/IrUnitHandlerInterface.hpp new file mode 100644 index 000000000..cb2f0ac18 --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/IrUnitHandlerInterface.hpp @@ -0,0 +1,62 @@ +#ifndef CLP_FFI_IR_STREAM_IRUNITHANDLERINTERFACE_HPP +#define CLP_FFI_IR_STREAM_IRUNITHANDLERINTERFACE_HPP + +#include +#include + +#include "../../time_types.hpp" +#include "../KeyValuePairLogEvent.hpp" +#include "../SchemaTree.hpp" +#include "decoding_methods.hpp" + +namespace clp::ffi::ir_stream { +/** + * Concept that defines the IR unit handler interface. + */ +template +concept IrUnitHandlerInterface = requires( + Handler handler, + KeyValuePairLogEvent&& log_event, + UtcOffset utc_offset_old, + UtcOffset utc_offset_new, + SchemaTree::NodeLocator schema_tree_node_locator +) { + /** + * Handles a log event IR unit. + * @param log_event The deserialized result from IR deserializer. + * @return IRErrorCode::Success on success, user-defined error code on failures. + */ + { + handler.handle_log_event(std::forward(log_event)) + } -> std::same_as; + + /** + * Handles a UTC offset change IR unit. + * @param utc_offset_old The offset before the change. + * @param utc_offset_new The deserialized new offset. + * @return IRErrorCode::Success on success, user-defined error code on failures. + */ + { + handler.handle_utc_offset_change(utc_offset_old, utc_offset_new) + } -> std::same_as; + + /** + * Handles a schema tree node insertion IR unit. + * @param schema_tree_node_locator The locator of the node to insert. + * @return IRErrorCode::Success on success, user-defined error code on failures. + */ + { + handler.handle_schema_tree_node_insertion(schema_tree_node_locator) + } -> std::same_as; + + /** + * Handles an end-of-stream indicator IR unit. + * @return IRErrorCode::Success on success, user-defined error code on failures. + */ + { + handler.handle_end_of_stream() + } -> std::same_as; +}; +} // namespace clp::ffi::ir_stream + +#endif // CLP_FFI_IR_STREAM_IRUNITHANDLERINTERFACE_HPP diff --git a/components/core/src/clp/ffi/ir_stream/IrUnitType.hpp b/components/core/src/clp/ffi/ir_stream/IrUnitType.hpp new file mode 100644 index 000000000..1ac38750f --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/IrUnitType.hpp @@ -0,0 +1,18 @@ +#ifndef CLP_FFI_IR_STREAM_IRUNITTYPE_HPP +#define CLP_FFI_IR_STREAM_IRUNITTYPE_HPP + +#include + +namespace clp::ffi::ir_stream { +/** + * Enum defining the possible IR unit types in CLP kv-pair IR stream. + */ +enum class IrUnitType : uint8_t { + LogEvent = 0, + SchemaTreeNodeInsertion, + UtcOffsetChange, + EndOfStream, +}; +} // namespace clp::ffi::ir_stream + +#endif // CLP_FFI_IR_STREAM_IRUNITTYPE_HPP diff --git a/components/core/src/clp/ffi/ir_stream/Serializer.cpp b/components/core/src/clp/ffi/ir_stream/Serializer.cpp new file mode 100644 index 000000000..b29cf0492 --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/Serializer.cpp @@ -0,0 +1,527 @@ +#include "Serializer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../../ir/types.hpp" +#include "../../time_types.hpp" +#include "../../type_utils.hpp" +#include "../encoding_methods.hpp" +#include "../SchemaTree.hpp" +#include "encoding_methods.hpp" +#include "protocol_constants.hpp" +#include "utils.hpp" + +using std::optional; +using std::span; +using std::string; +using std::string_view; +using std::vector; + +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::four_byte_encoded_variable_t; + +namespace clp::ffi::ir_stream { +namespace { +/** + * Class for iterating the kv-pairs of a MessagePack map. + */ +class MsgpackMapIterator { +public: + // Types + using Child = msgpack::object_kv; + + // Constructors + MsgpackMapIterator(SchemaTree::Node::id_t schema_tree_node_id, span children) + : m_schema_tree_node_id{schema_tree_node_id}, + m_children{children}, + m_curr_child_it{m_children.begin()} {} + + // Methods + /** + * @return This map's ID in the schema tree. + */ + [[nodiscard]] auto get_schema_tree_node_id() const -> SchemaTree::Node::id_t { + return m_schema_tree_node_id; + } + + /** + * @return Whether there are more children to traverse. + */ + [[nodiscard]] auto has_next_child() const -> bool { + return m_curr_child_it != m_children.end(); + } + + /** + * Gets the next child and advances the underlying child idx. + * @return The next child to traverse. + */ + [[nodiscard]] auto get_next_child() -> Child const& { return *(m_curr_child_it++); } + +private: + SchemaTree::Node::id_t m_schema_tree_node_id; + span m_children; + span::iterator m_curr_child_it; +}; + +/** + * Gets the schema-tree node type that corresponds with a given MessagePack value. + * @param val + * @return The corresponding schema-tree node type. + * @return std::nullopt if the value doesn't match any of the supported schema-tree node types. + */ +[[nodiscard]] auto get_schema_tree_node_type_from_msgpack_val(msgpack::object const& val +) -> optional; + +/** + * Serializes an empty object. + * @param output_buf + */ +auto serialize_value_empty_object(vector& output_buf) -> void; + +/** + * Serializes an integer. + * @param val + * @param output_buf + * @return Whether serialization succeeded. + */ +auto serialize_value_int(int64_t val, vector& output_buf) -> void; + +/** + * Serializes a float. + * @param val + * @param output_buf + */ +auto serialize_value_float(double val, vector& output_buf) -> void; + +/** + * Serializes a boolean. + * @param val + * @param output_buf + */ +auto serialize_value_bool(bool val, vector& output_buf) -> void; + +/** + * Serializes a null. + * @param output_buf + */ +auto serialize_value_null(vector& output_buf) -> void; + +/** + * Serializes a string. + * @tparam encoded_variable_t + * @param val + * @param logtype_buf + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto +serialize_value_string(string_view val, string& logtype_buf, vector& output_buf) -> bool; + +/** + * Serializes a MessagePack array as a JSON string, using CLP's encoding for unstructured text. + * @tparam encoded_variable_t + * @param val + * @param logtype_buf + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto serialize_value_array( + msgpack::object const& val, + string& logtype_buf, + vector& output_buf +) -> bool; + +auto get_schema_tree_node_type_from_msgpack_val(msgpack::object const& val +) -> optional { + optional ret_val; + switch (val.type) { + case msgpack::type::POSITIVE_INTEGER: + case msgpack::type::NEGATIVE_INTEGER: + ret_val.emplace(SchemaTree::Node::Type::Int); + break; + case msgpack::type::FLOAT32: + case msgpack::type::FLOAT64: + ret_val.emplace(SchemaTree::Node::Type::Float); + break; + case msgpack::type::STR: + ret_val.emplace(SchemaTree::Node::Type::Str); + break; + case msgpack::type::BOOLEAN: + ret_val.emplace(SchemaTree::Node::Type::Bool); + break; + case msgpack::type::NIL: + case msgpack::type::MAP: + ret_val.emplace(SchemaTree::Node::Type::Obj); + break; + case msgpack::type::ARRAY: + ret_val.emplace(SchemaTree::Node::Type::UnstructuredArray); + break; + default: + return std::nullopt; + } + return ret_val; +} + +auto serialize_value_empty_object(vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueEmpty); +} + +auto serialize_value_int(int64_t val, vector& output_buf) -> void { + if (INT8_MIN <= val && val <= INT8_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt8); + output_buf.push_back(static_cast(val)); + } else if (INT16_MIN <= val && val <= INT16_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt16); + serialize_int(static_cast(val), output_buf); + } else if (INT32_MIN <= val && val <= INT32_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt32); + serialize_int(static_cast(val), output_buf); + } else { // (INT64_MIN <= val && val <= INT64_MAX) + output_buf.push_back(cProtocol::Payload::ValueInt64); + serialize_int(val, output_buf); + } +} + +auto serialize_value_float(double val, vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueFloat); + serialize_int(bit_cast(val), output_buf); +} + +auto serialize_value_bool(bool val, vector& output_buf) -> void { + output_buf.push_back(val ? cProtocol::Payload::ValueTrue : cProtocol::Payload::ValueFalse); +} + +auto serialize_value_null(vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueNull); +} + +template +auto serialize_value_string(string_view val, string& logtype_buf, vector& output_buf) + -> bool { + if (string_view::npos == val.find(' ')) { + return serialize_string(val, output_buf); + } + logtype_buf.clear(); + return serialize_clp_string(val, logtype_buf, output_buf); +} + +template +auto serialize_value_array( + msgpack::object const& val, + string& logtype_buf, + vector& output_buf +) -> bool { + std::ostringstream oss; + oss << val; + logtype_buf.clear(); + return serialize_clp_string(oss.str(), logtype_buf, output_buf); +} +} // namespace + +template +auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result> { + static_assert( + (std::is_same_v + || std::is_same_v) + ); + + Serializer serializer; + auto& ir_buf{serializer.m_ir_buf}; + constexpr BufferView cMagicNumber{ + static_cast( + std::is_same_v + ? cProtocol::EightByteEncodingMagicNumber + : cProtocol::FourByteEncodingMagicNumber + ), + cProtocol::MagicNumberLength + }; + ir_buf.insert(ir_buf.cend(), cMagicNumber.begin(), cMagicNumber.end()); + + nlohmann::json metadata; + metadata.emplace(cProtocol::Metadata::VersionKey, cProtocol::Metadata::VersionValue); + metadata.emplace(cProtocol::Metadata::VariablesSchemaIdKey, cVariablesSchemaVersion); + metadata.emplace( + cProtocol::Metadata::VariableEncodingMethodsIdKey, + cVariableEncodingMethodsVersion + ); + if (false == serialize_metadata(metadata, ir_buf)) { + return std::errc::protocol_error; + } + + return serializer; +} + +template +auto Serializer::change_utc_offset(UtcOffset utc_offset) -> void { + if (utc_offset != m_curr_utc_offset) { + m_curr_utc_offset = utc_offset; + } + serialize_utc_offset_change(m_curr_utc_offset, m_ir_buf); +} + +template +auto Serializer::serialize_msgpack_map(msgpack::object_map const& msgpack_map +) -> bool { + if (0 == msgpack_map.size) { + serialize_value_empty_object(m_ir_buf); + return true; + } + + m_schema_tree.take_snapshot(); + m_schema_tree_node_buf.clear(); + m_key_group_buf.clear(); + m_value_group_buf.clear(); + + // Traverse the map using DFS iteratively + bool failure{false}; + vector dfs_stack; + dfs_stack.emplace_back( + SchemaTree::cRootId, + span{msgpack_map.ptr, msgpack_map.size} + ); + while (false == dfs_stack.empty()) { + auto& curr{dfs_stack.back()}; + if (false == curr.has_next_child()) { + // Visited all children, so pop node + dfs_stack.pop_back(); + continue; + } + + // Convert the current value's type to its corresponding schema-tree node type + auto const& [key, val]{curr.get_next_child()}; + auto const opt_schema_tree_node_type{get_schema_tree_node_type_from_msgpack_val(val)}; + if (false == opt_schema_tree_node_type.has_value()) { + failure = true; + break; + } + auto const schema_tree_node_type{opt_schema_tree_node_type.value()}; + + SchemaTree::NodeLocator const locator{ + curr.get_schema_tree_node_id(), + key.as(), + schema_tree_node_type + }; + + // Get the schema-tree node that corresponds with the current kv-pair, or add it if it + // doesn't exist. + auto opt_schema_tree_node_id{m_schema_tree.try_get_node_id(locator)}; + if (false == opt_schema_tree_node_id.has_value()) { + opt_schema_tree_node_id.emplace(m_schema_tree.insert_node(locator)); + if (false == serialize_schema_tree_node(locator)) { + failure = true; + break; + } + } + auto const schema_tree_node_id{opt_schema_tree_node_id.value()}; + + if (msgpack::type::MAP == val.type) { + // Serialize map + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + auto const& inner_map{val.via.map}; + auto const inner_map_size(inner_map.size); + if (0 == inner_map_size) { + // Value is an empty map, so we can serialize it immediately + if (false == serialize_key(schema_tree_node_id)) { + failure = true; + break; + } + serialize_value_empty_object(m_value_group_buf); + } else { + // Add map for DFS iteration + dfs_stack.emplace_back( + schema_tree_node_id, + span{inner_map.ptr, inner_map_size} + ); + } + } else { + // Serialize primitive + if (false + == (serialize_key(schema_tree_node_id) && serialize_val(val, schema_tree_node_type) + )) + { + failure = true; + break; + } + } + } + + if (failure) { + m_schema_tree.revert(); + return false; + } + + m_ir_buf.insert( + m_ir_buf.cend(), + m_schema_tree_node_buf.cbegin(), + m_schema_tree_node_buf.cend() + ); + m_ir_buf.insert(m_ir_buf.cend(), m_key_group_buf.cbegin(), m_key_group_buf.cend()); + m_ir_buf.insert(m_ir_buf.cend(), m_value_group_buf.cbegin(), m_value_group_buf.cend()); + return true; +} + +template +auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool { + switch (locator.get_type()) { + case SchemaTree::Node::Type::Int: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeInt); + break; + case SchemaTree::Node::Type::Float: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeFloat); + break; + case SchemaTree::Node::Type::Bool: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeBool); + break; + case SchemaTree::Node::Type::Str: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeStr); + break; + case SchemaTree::Node::Type::UnstructuredArray: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeUnstructuredArray); + break; + case SchemaTree::Node::Type::Obj: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeObj); + break; + default: + // Unknown type + return false; + } + + if (false + == encode_and_serialize_schema_tree_node_id< + false, + cProtocol::Payload::EncodedSchemaTreeNodeParentIdByte, + cProtocol::Payload::EncodedSchemaTreeNodeParentIdShort, + cProtocol::Payload::EncodedSchemaTreeNodeParentIdInt>( + locator.get_parent_id(), + m_schema_tree_node_buf + )) + { + return false; + } + + return serialize_string(locator.get_key_name(), m_schema_tree_node_buf); +} + +template +auto Serializer::serialize_key(SchemaTree::Node::id_t id) -> bool { + return encode_and_serialize_schema_tree_node_id< + false, + cProtocol::Payload::EncodedSchemaTreeNodeIdByte, + cProtocol::Payload::EncodedSchemaTreeNodeIdShort, + cProtocol::Payload::EncodedSchemaTreeNodeIdInt>(id, m_key_group_buf); +} + +template +auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTree::Node::Type schema_tree_node_type +) -> bool { + switch (schema_tree_node_type) { + case SchemaTree::Node::Type::Int: + if (msgpack::type::POSITIVE_INTEGER == val.type + && static_cast(INT64_MAX) < val.as()) + { + return false; + } + serialize_value_int(val.as(), m_value_group_buf); + break; + + case SchemaTree::Node::Type::Float: + serialize_value_float(val.as(), m_value_group_buf); + break; + + case SchemaTree::Node::Type::Bool: + serialize_value_bool(val.as(), m_value_group_buf); + break; + + case SchemaTree::Node::Type::Str: + if (false + == serialize_value_string( + val.as(), + m_logtype_buf, + m_value_group_buf + )) + { + return false; + } + break; + + case SchemaTree::Node::Type::Obj: + if (msgpack::type::NIL != val.type) { + return false; + } + serialize_value_null(m_value_group_buf); + break; + + case SchemaTree::Node::Type::UnstructuredArray: + if (false + == serialize_value_array(val, m_logtype_buf, m_value_group_buf)) + { + return false; + } + break; + + default: + // Unknown schema tree node type + return false; + } + return true; +} + +// Explicitly declare template specializations so that we can define the template methods in this +// file +template auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result>; +template auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result>; + +template auto Serializer::change_utc_offset(UtcOffset utc_offset +) -> void; +template auto Serializer::change_utc_offset(UtcOffset utc_offset +) -> void; + +template auto Serializer::serialize_msgpack_map( + msgpack::object_map const& msgpack_map +) -> bool; +template auto Serializer::serialize_msgpack_map( + msgpack::object_map const& msgpack_map +) -> bool; + +template auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool; +template auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool; + +template auto Serializer::serialize_key(SchemaTree::Node::id_t id +) -> bool; +template auto Serializer::serialize_key(SchemaTree::Node::id_t id +) -> bool; + +template auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTree::Node::Type schema_tree_node_type +) -> bool; +template auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTree::Node::Type schema_tree_node_type +) -> bool; +} // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/Serializer.hpp b/components/core/src/clp/ffi/ir_stream/Serializer.hpp new file mode 100644 index 000000000..14077ffba --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/Serializer.hpp @@ -0,0 +1,130 @@ +#ifndef CLP_FFI_IR_STREAM_SERIALIZER_HPP +#define CLP_FFI_IR_STREAM_SERIALIZER_HPP + +#include +#include +#include +#include + +#include +#include + +#include "../../time_types.hpp" +#include "../SchemaTree.hpp" + +namespace clp::ffi::ir_stream { +/** + * Class for serializing log events into the kv-pair IR format. + * + * This class: + * - maintains all necessary internal data structures to track serialization state; + * - provides APIs to serialize log events into the IR format; and + * - provides APIs to access the serialized IR bytes. + * + * NOTE: + * - This class is designed only to provide serialization functionalities. Callers are responsible + * for writing the serialized bytes into I/O streams. + * - This class doesn't provide an API to terminate the IR stream. Callers should + * terminate the stream by flushing this class' IR buffer to the I/O stream and then writing + * `clp::ffi::ir_stream::cProtocol::Eof` to the I/O stream. + * @tparam encoded_variable_t Type of encoded variables in the serialized IR stream. + */ +template +class Serializer { +public: + // Types + using Buffer = std::vector; + using BufferView = std::span; + + // Factory functions + /** + * Creates an IR serializer and serializes the stream's preamble. + * @return A result containing the serializer or an error code indicating the failure: + * - std::errc::protocol_error on failure to serialize the preamble. + */ + [[nodiscard]] static auto create( + ) -> OUTCOME_V2_NAMESPACE::std_result>; + + // Disable copy constructor/assignment operator + Serializer(Serializer const&) = delete; + auto operator=(Serializer const&) -> Serializer& = delete; + + // Define default move constructor/assignment operator + Serializer(Serializer&&) = default; + auto operator=(Serializer&&) -> Serializer& = default; + + // Destructor + ~Serializer() = default; + + // Methods + /** + * @return A view of the underlying IR buffer which contains the serialized IR bytes. + */ + [[nodiscard]] auto get_ir_buf_view() const -> BufferView { + return {m_ir_buf.data(), m_ir_buf.size()}; + } + + /** + * Clears the underlying IR buffer. + */ + auto clear_ir_buf() -> void { m_ir_buf.clear(); } + + /** + * @return The current UTC offset. + */ + [[nodiscard]] auto get_curr_utc_offset() const -> UtcOffset { return m_curr_utc_offset; } + + /** + * Changes the UTC offset and serializes a UTC offset change packet, if the given UTC offset is + * different than the current UTC offset. + * @param utc_offset + */ + auto change_utc_offset(UtcOffset utc_offset) -> void; + + /** + * Serializes the given msgpack map as a key-value pair log event. + * @param msgpack_map + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto serialize_msgpack_map(msgpack::object_map const& msgpack_map) -> bool; + +private: + // Constructors + Serializer() = default; + + // Methods + /** + * Serializes a schema tree node identified by the given locator into `m_schema_tree_node_buf`. + * @param locator + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto serialize_schema_tree_node(SchemaTree::NodeLocator const& locator) -> bool; + + /** + * Serializes the given key ID into `m_key_group_buf`. + * @param id + * @return Forwards `encode_and_serialize_schema_tree_node_id`'s return values. + */ + [[nodiscard]] auto serialize_key(SchemaTree::Node::id_t id) -> bool; + + /** + * Serializes the given MessagePack value into `m_value_group_buf`. + * @param val + * @param schema_tree_node_type The type of the schema tree node that corresponds to `val`. + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto + serialize_val(msgpack::object const& val, SchemaTree::Node::Type schema_tree_node_type) -> bool; + + UtcOffset m_curr_utc_offset{0}; + Buffer m_ir_buf; + SchemaTree m_schema_tree; + + std::string m_logtype_buf; + Buffer m_schema_tree_node_buf; + Buffer m_key_group_buf; + Buffer m_value_group_buf; +}; +} // namespace clp::ffi::ir_stream + +#endif diff --git a/components/core/src/clp/ffi/ir_stream/decoding_methods.cpp b/components/core/src/clp/ffi/ir_stream/decoding_methods.cpp index 29f57df0a..f61efc1df 100644 --- a/components/core/src/clp/ffi/ir_stream/decoding_methods.cpp +++ b/components/core/src/clp/ffi/ir_stream/decoding_methods.cpp @@ -1,10 +1,15 @@ #include "decoding_methods.hpp" +#include +#include #include +#include +#include #include "../../ir/types.hpp" #include "byteswap.hpp" #include "protocol_constants.hpp" +#include "utils.hpp" using clp::ir::eight_byte_encoded_variable_t; using clp::ir::epoch_time_ms_t; @@ -24,16 +29,6 @@ namespace clp::ffi::ir_stream { template static bool is_variable_tag(encoded_tag_t tag, bool& is_encoded_var); -/** - * Deserializes an integer from the given reader - * @tparam integer_t Type of the integer to deserialize - * @param reader - * @param value Returns the deserialized integer - * @return true on success, false if the reader doesn't contain enough data to deserialize - */ -template -static bool deserialize_int(ReaderInterface& reader, integer_t& value); - /** * Deserializes a logtype from the given reader * @param reader @@ -138,27 +133,6 @@ static bool is_variable_tag(encoded_tag_t tag, bool& is_encoded_var) { return false; } -template -static bool deserialize_int(ReaderInterface& reader, integer_t& value) { - integer_t value_little_endian; - if (reader.try_read_numeric_value(value_little_endian) != ErrorCode_Success) { - return false; - } - - constexpr auto read_size = sizeof(integer_t); - static_assert(read_size == 1 || read_size == 2 || read_size == 4 || read_size == 8); - if constexpr (read_size == 1) { - value = value_little_endian; - } else if constexpr (read_size == 2) { - value = bswap_16(value_little_endian); - } else if constexpr (read_size == 4) { - value = bswap_32(value_little_endian); - } else if constexpr (read_size == 8) { - value = bswap_64(value_little_endian); - } - return true; -} - static IRErrorCode deserialize_logtype(ReaderInterface& reader, encoded_tag_t encoded_tag, string& logtype) { size_t logtype_length; @@ -362,6 +336,38 @@ auto deserialize_log_event( vector& encoded_vars, vector& dict_vars, epoch_time_ms_t& timestamp_or_timestamp_delta +) -> IRErrorCode { + if (auto const err + = deserialize_encoded_text_ast(reader, encoded_tag, logtype, encoded_vars, dict_vars); + IRErrorCode_Success != err) + { + return err; + } + + // NOTE: for the eight-byte encoding, the timestamp is the actual timestamp; for the four-byte + // encoding, the timestamp is a timestamp delta + if (ErrorCode_Success != reader.try_read_numeric_value(encoded_tag)) { + return IRErrorCode_Incomplete_IR; + } + if (auto error_code = deserialize_timestamp( + reader, + encoded_tag, + timestamp_or_timestamp_delta + ); + IRErrorCode_Success != error_code) + { + return error_code; + } + return IRErrorCode_Success; +} + +template +auto deserialize_encoded_text_ast( + ReaderInterface& reader, + encoded_tag_t encoded_tag, + std::string& logtype, + std::vector& encoded_vars, + std::vector& dict_vars ) -> IRErrorCode { // Handle variables string var_str; @@ -393,20 +399,6 @@ auto deserialize_log_event( return error_code; } - // NOTE: for the eight-byte encoding, the timestamp is the actual timestamp; for the four-byte - // encoding, the timestamp is a timestamp delta - if (ErrorCode_Success != reader.try_read_numeric_value(encoded_tag)) { - return IRErrorCode_Incomplete_IR; - } - if (auto error_code = deserialize_timestamp( - reader, - encoded_tag, - timestamp_or_timestamp_delta - ); - IRErrorCode_Success != error_code) - { - return error_code; - } return IRErrorCode_Success; } @@ -480,13 +472,23 @@ IRErrorCode deserialize_preamble( return IRErrorCode_Success; } -IRProtocolErrorCode validate_protocol_version(std::string_view protocol_version) { - if ("v0.0.0" == protocol_version) { - // This version is hardcoded to support the oldest IR protocol version. When this version is - // no longer supported, this branch should be removed. - return IRProtocolErrorCode_Supported; +auto validate_protocol_version(std::string_view protocol_version) -> IRProtocolErrorCode { + // These versions are hardcoded to support the IR protocol version that predates the key-value + // pair IR format. + constexpr std::array cBackwardCompatibleVersions{ + "v0.0.0", + "0.0.1", + cProtocol::Metadata::LatestBackwardCompatibleVersion + }; + if (cBackwardCompatibleVersions.cend() + != std::ranges::find(cBackwardCompatibleVersions, protocol_version)) + { + return IRProtocolErrorCode::BackwardCompatible; } - std::regex const protocol_version_regex{cProtocol::Metadata::VersionRegex}; + + std::regex const protocol_version_regex{ + static_cast(cProtocol::Metadata::VersionRegex) + }; if (false == std::regex_match( protocol_version.begin(), @@ -494,19 +496,16 @@ IRProtocolErrorCode validate_protocol_version(std::string_view protocol_version) protocol_version_regex )) { - return IRProtocolErrorCode_Invalid; + return IRProtocolErrorCode::Invalid; } - std::string_view current_build_protocol_version{cProtocol::Metadata::VersionValue}; - auto get_major_version{[](std::string_view version) { - return version.substr(0, version.find('.')); - }}; - if (current_build_protocol_version < protocol_version) { - return IRProtocolErrorCode_Too_New; - } - if (get_major_version(current_build_protocol_version) > get_major_version(protocol_version)) { - return IRProtocolErrorCode_Too_Old; + + // TODO: Currently, we hardcode the supported versions. This should be removed once we + // implement a proper version parser. + if (cProtocol::Metadata::VersionValue == protocol_version) { + return IRProtocolErrorCode::Supported; } - return IRProtocolErrorCode_Supported; + + return IRProtocolErrorCode::Unsupported; } IRErrorCode deserialize_utc_offset_change(ReaderInterface& reader, UtcOffset& utc_offset) { @@ -568,4 +567,20 @@ template auto deserialize_log_event( vector& dict_vars, epoch_time_ms_t& timestamp_or_timestamp_delta ) -> IRErrorCode; + +template auto deserialize_encoded_text_ast( + ReaderInterface& reader, + encoded_tag_t encoded_tag, + std::string& logtype, + std::vector& encoded_vars, + std::vector& dict_vars +) -> IRErrorCode; + +template auto deserialize_encoded_text_ast( + ReaderInterface& reader, + encoded_tag_t encoded_tag, + std::string& logtype, + std::vector& encoded_vars, + std::vector& dict_vars +) -> IRErrorCode; } // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/decoding_methods.hpp b/components/core/src/clp/ffi/ir_stream/decoding_methods.hpp index 982b9c7aa..a9bc5c4fd 100644 --- a/components/core/src/clp/ffi/ir_stream/decoding_methods.hpp +++ b/components/core/src/clp/ffi/ir_stream/decoding_methods.hpp @@ -1,6 +1,7 @@ #ifndef CLP_FFI_IR_STREAM_DECODING_METHODS_HPP #define CLP_FFI_IR_STREAM_DECODING_METHODS_HPP +#include #include #include @@ -20,12 +21,12 @@ typedef enum { IRErrorCode_Incomplete_IR, } IRErrorCode; -typedef enum { - IRProtocolErrorCode_Supported, - IRProtocolErrorCode_Too_Old, - IRProtocolErrorCode_Too_New, - IRProtocolErrorCode_Invalid, -} IRProtocolErrorCode; +enum class IRProtocolErrorCode : uint8_t { + Supported, + BackwardCompatible, + Unsupported, + Invalid, +}; class DecodingException : public TraceableException { public: @@ -89,6 +90,27 @@ auto deserialize_log_event( ir::epoch_time_ms_t& timestamp_or_timestamp_delta ) -> IRErrorCode; +/** + * Deserializes an encoded text AST from the given stream + * @tparam encoded_variable_t + * @param reader + * @param encoded_tag Tag of the next packet to read + * @param logtype Returns the logtype + * @param encoded_vars Returns the encoded variables + * @param dict_vars Returns the dictionary variables + * @return IRErrorCode_Success on success + * @return IRErrorCode_Corrupted_IR if `reader` contains invalid IR + * @return IRErrorCode_Incomplete_IR if `reader` doesn't contain enough data + */ +template +auto deserialize_encoded_text_ast( + ReaderInterface& reader, + encoded_tag_t encoded_tag, + std::string& logtype, + std::vector& encoded_vars, + std::vector& dict_vars +) -> IRErrorCode; + /** * Decodes the IR message calls the given methods to handle each component of the message * @tparam unescape_logtype Whether to remove the escape characters from the logtype before calling @@ -172,15 +194,19 @@ IRErrorCode deserialize_utc_offset_change(ReaderInterface& reader, UtcOffset& ut /** * Validates whether the given protocol version can be supported by the current build. * @param protocol_version - * @return IRProtocolErrorCode_Supported if the protocol version is supported. - * @return IRProtocolErrorCode_Too_Old if the protocol version is no longer supported by this - * build's protocol version. - * @return IRProtocolErrorCode_Too_New if the protocol version is newer than this build's protocol - * version. - * @return IRProtocolErrorCode_Invalid if the protocol version does not follow the SemVer + * @return IRProtocolErrorCode::Supported if the protocol version is supported by the key-value + * pair IR stream serializer and deserializer. TODO: Update this once we integrate backwards + * compatibility into the deserializer. + * @return IRProtocolErrorCode::BackwardCompatible if the protocol version is supported by the + * serializer and deserializer for the IR stream format that predates the key-value pair IR stream + * format. TODO: Update this once we integrate backwards compatibility into the key-value pair IR + * stream format. + * @return IRProtocolErrorCode::Unsupported if the protocol version is not supported by this build. + * @return IRProtocolErrorCode::Invalid if the protocol version does not follow the SemVer * specification. */ -IRProtocolErrorCode validate_protocol_version(std::string_view protocol_version); +[[nodiscard]] auto validate_protocol_version(std::string_view protocol_version +) -> IRProtocolErrorCode; namespace eight_byte_encoding { /** diff --git a/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp b/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp index 682972e9d..7c036de0d 100644 --- a/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp +++ b/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp @@ -5,8 +5,8 @@ #include "../../ir/parsing.hpp" #include "../../ir/types.hpp" #include "../../time_types.hpp" -#include "byteswap.hpp" #include "protocol_constants.hpp" +#include "utils.hpp" using clp::ir::eight_byte_encoded_variable_t; using clp::ir::epoch_time_ms_t; @@ -17,15 +17,6 @@ using std::vector; namespace clp::ffi::ir_stream { // Local function prototypes -/** - * Serializes the given integer into the IR stream - * @tparam integer_t - * @param value - * @param ir_buf - */ -template -static void serialize_int(integer_t value, vector& ir_buf); - /** * Serializes the given logtype into the IR stream * @param logtype @@ -34,14 +25,6 @@ static void serialize_int(integer_t value, vector& ir_buf); */ static bool serialize_logtype(string_view logtype, vector& ir_buf); -/** - * Serializes the given metadata into the IR stream - * @param metadata - * @param ir_buf - * @return true on success, false otherwise - */ -static bool serialize_metadata(nlohmann::json& metadata, vector& ir_buf); - /** * Adds the basic metadata fields to the given JSON object * @param timestamp_pattern @@ -90,21 +73,6 @@ class DictionaryVariableHandler { vector& m_ir_buf; }; -template -static void serialize_int(integer_t value, vector& ir_buf) { - integer_t value_big_endian; - static_assert(sizeof(integer_t) == 2 || sizeof(integer_t) == 4 || sizeof(integer_t) == 8); - if constexpr (sizeof(value) == 2) { - value_big_endian = bswap_16(value); - } else if constexpr (sizeof(value) == 4) { - value_big_endian = bswap_32(value); - } else if constexpr (sizeof(value) == 8) { - value_big_endian = bswap_64(value); - } - auto data = reinterpret_cast(&value_big_endian); - ir_buf.insert(ir_buf.end(), data, data + sizeof(value)); -} - static bool serialize_logtype(string_view logtype, vector& ir_buf) { auto length = logtype.length(); if (length <= UINT8_MAX) { @@ -124,34 +92,14 @@ static bool serialize_logtype(string_view logtype, vector& ir_buf) { return true; } -static bool serialize_metadata(nlohmann::json& metadata, vector& ir_buf) { - ir_buf.push_back(cProtocol::Metadata::EncodingJson); - - auto metadata_serialized - = metadata.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore); - auto metadata_serialized_length = metadata_serialized.length(); - if (metadata_serialized_length <= UINT8_MAX) { - ir_buf.push_back(cProtocol::Metadata::LengthUByte); - ir_buf.push_back(bit_cast(static_cast(metadata_serialized_length))); - } else if (metadata_serialized_length <= UINT16_MAX) { - ir_buf.push_back(cProtocol::Metadata::LengthUShort); - serialize_int(static_cast(metadata_serialized_length), ir_buf); - } else { - // Can't encode metadata longer than 64 KiB - return false; - } - ir_buf.insert(ir_buf.cend(), metadata_serialized.cbegin(), metadata_serialized.cend()); - - return true; -} - static void add_base_metadata_fields( string_view timestamp_pattern, string_view timestamp_pattern_syntax, string_view time_zone_id, nlohmann::json& metadata ) { - metadata[cProtocol::Metadata::VersionKey] = cProtocol::Metadata::VersionValue; + metadata[cProtocol::Metadata::VersionKey] + = cProtocol::Metadata::LatestBackwardCompatibleVersion; metadata[cProtocol::Metadata::VariablesSchemaIdKey] = cVariablesSchemaVersion; metadata[cProtocol::Metadata::VariableEncodingMethodsIdKey] = cVariableEncodingMethodsVersion; metadata[cProtocol::Metadata::TimestampPatternKey] = timestamp_pattern; @@ -188,6 +136,22 @@ bool serialize_log_event( string_view message, string& logtype, vector& ir_buf +) { + if (false == serialize_message(message, logtype, ir_buf)) { + return false; + } + + // Encode timestamp + ir_buf.push_back(cProtocol::Payload::TimestampVal); + serialize_int(timestamp, ir_buf); + + return true; +} + +bool serialize_message( + std::string_view message, + std::string& logtype, + std::vector& ir_buf ) { auto encoded_var_handler = [&ir_buf](eight_byte_encoded_variable_t encoded_var) { ir_buf.push_back(cProtocol::Payload::VarEightByteEncoding); @@ -206,15 +170,7 @@ bool serialize_log_event( return false; } - if (false == serialize_logtype(logtype, ir_buf)) { - return false; - } - - // Encode timestamp - ir_buf.push_back(cProtocol::Payload::TimestampVal); - serialize_int(timestamp, ir_buf); - - return true; + return serialize_logtype(logtype, ir_buf); } } // namespace eight_byte_encoding diff --git a/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp b/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp index 0129e7550..be042e870 100644 --- a/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp +++ b/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp @@ -39,6 +39,15 @@ bool serialize_log_event( std::string& logtype, std::vector& ir_buf ); + +/** + * Serializes the given message into the eight-byte encoding IR stream. + * @param message + * @param logtype Returns the message's logtype. + * @param ir_buf + * @return Whether the message was serialized successfully. + */ +bool serialize_message(std::string_view message, std::string& logtype, std::vector& ir_buf); } // namespace eight_byte_encoding namespace four_byte_encoding { diff --git a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp new file mode 100644 index 000000000..5e1813a3e --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.cpp @@ -0,0 +1,587 @@ +#include "ir_unit_deserialization_methods.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../ErrorCode.hpp" +#include "../../ir/EncodedTextAst.hpp" +#include "../../ir/types.hpp" +#include "../../ReaderInterface.hpp" +#include "../../time_types.hpp" +#include "../../type_utils.hpp" +#include "../KeyValuePairLogEvent.hpp" +#include "../SchemaTree.hpp" +#include "../Value.hpp" +#include "decoding_methods.hpp" +#include "IrUnitType.hpp" +#include "protocol_constants.hpp" +#include "utils.hpp" + +namespace clp::ffi::ir_stream { +namespace { +/** + * A collection of schema tree leaf node IDs. It represents the schema of a `KeyValuePairLogEvent`. + */ +using Schema = std::vector; + +/** + * @param tag + * @return The corresponding schema tree node type on success. + * @return std::nullopt if the tag doesn't match to any defined schema tree node type. + */ +[[nodiscard]] auto schema_tree_node_tag_to_type(encoded_tag_t tag +) -> std::optional; + +/** + * Deserializes the parent ID of a schema tree node. + * @param reader + * @return A result containing a pair or an error code indicating the failure: + * - The pair: + * - Whether the node ID is for an auto-generated node. + * - The decoded node ID. + * - The possible error codes: + * - Forwards `deserialize_tag`'s return values. + * @return Forwards `deserialize_and_decode_schema_tree_node_id`'s return values. + */ +[[nodiscard]] auto deserialize_schema_tree_node_parent_id(ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result>; + +/** + * Deserializes the key name of a schema tree node. + * @param reader + * @param key_name Returns the deserialized key name. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return Forwards `deserialize_tag`'s return values on failure. + * @return Forwards `deserialize_string`'s return values on failure. + */ +[[nodiscard]] auto deserialize_schema_tree_node_key_name( + ReaderInterface& reader, + std::string& key_name +) -> IRErrorCode; + +/** + * Deserializes an integer value packet. + * @param reader + * @param tag + * @param val Returns the deserialized value. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return IRErrorCode::IRErrorCode_Incomplete_IR if the stream is truncated. + * @return IRErrorCode::IRErrorCode_Corrupted_IR if the given tag doesn't correspond to an integer + * packet. + */ +[[nodiscard]] auto +deserialize_int_val(ReaderInterface& reader, encoded_tag_t tag, value_int_t& val) -> IRErrorCode; + +/** + * Deserializes a string packet. + * @param reader + * @param tag + * @param deserialized_str Returns the deserialized string. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return IRErrorCode::IRErrorCode_Incomplete_IR if the stream is truncated. + * @return IRErrorCode::IRErrorCode_Corrupted_IR if the given tag doesn't correspond to a string + * packet. + */ +[[nodiscard]] auto deserialize_string( + ReaderInterface& reader, + encoded_tag_t tag, + std::string& deserialized_str +) -> IRErrorCode; + +/** + * Deserializes the IDs of all keys in a log event. + * @param reader + * @param tag Takes the current tag as input and returns the last tag read. + * @return A result containing the deserialized schema or an error code indicating the failure: + * - std::err::protocol_not_supported if the IR stream contains auto-generated keys (TODO: Remove + * this once auto-generated keys are fully supported). + * - Forwards `deserialize_tag`'s return values. + * - Forwards `deserialize_and_decode_schema_tree_node_id`'s return values. + */ +[[nodiscard]] auto deserialize_schema(ReaderInterface& reader, encoded_tag_t& tag) + -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * Deserializes the next value and pushes the result into `node_id_value_pairs`. + * @param reader + * @param tag + * @param node_id The node ID that corresponds to the value. + * @param node_id_value_pairs Returns the ID-value pair constructed from the deserialized value. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return IRErrorCode::IRErrorCode_Incomplete_IR if the stream is truncated. + * @return IRErrorCode::IRErrorCode_Corrupted_IR if the tag doesn't correspond to any known value + * type. + * @return Forwards `deserialize_encoded_text_ast_and_insert_to_node_id_value_pairs`'s return + * values on any other failure. + */ +[[nodiscard]] auto deserialize_value_and_insert_to_node_id_value_pairs( + ReaderInterface& reader, + encoded_tag_t tag, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode; + +/** + * Deserializes an encoded text AST and pushes the result into node_id_value_pairs. + * @tparam encoded_variable_t + * @param reader + * @param node_id The node ID that corresponds to the value. + * @param node_id_value_pairs Returns the ID-value pair constructed by the deserialized encoded text + * AST. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return Forwards `deserialize_tag`'s return values on failure. + * @return Forwards `deserialize_encoded_text_ast`'s return values on failure. + */ +template +requires(std::is_same_v + || std::is_same_v) +[[nodiscard]] auto deserialize_encoded_text_ast_and_insert_to_node_id_value_pairs( + ReaderInterface& reader, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode; + +/** + * Deserializes values and constructs ID-value pairs according to the given schema. The number of + * values to deserialize is indicated by the size of the given schema. + * @param reader + * @param tag + * @param schema The log event's schema. + * @param node_id_value_pairs Returns the constructed ID-value pairs. + * @return IRErrorCode::IRErrorCode_Success on success. + * @return IRErrorCode::IRErrorCode_Corrupted_IR if a key is duplicated in the deserialized log + * event. + * @return Forwards `deserialize_tag`'s return values on failure. + * @return Forwards `deserialize_value_and_insert_to_node_id_value_pairs`'s return values on + * failure. + */ +[[nodiscard]] auto deserialize_value_and_construct_node_id_value_pairs( + ReaderInterface& reader, + encoded_tag_t tag, + Schema const& schema, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode; + +/** + * @param tag + * @return Whether the given tag can be a valid leading tag of a log event IR unit. + */ +[[nodiscard]] auto is_log_event_ir_unit_tag(encoded_tag_t tag) -> bool; + +/** + * @param tag + * @return Whether the given tag represents a valid encoded key ID. + */ +[[nodiscard]] auto is_encoded_key_id_tag(encoded_tag_t tag) -> bool; + +auto schema_tree_node_tag_to_type(encoded_tag_t tag) -> std::optional { + switch (tag) { + case cProtocol::Payload::SchemaTreeNodeInt: + return SchemaTree::Node::Type::Int; + case cProtocol::Payload::SchemaTreeNodeFloat: + return SchemaTree::Node::Type::Float; + case cProtocol::Payload::SchemaTreeNodeBool: + return SchemaTree::Node::Type::Bool; + case cProtocol::Payload::SchemaTreeNodeStr: + return SchemaTree::Node::Type::Str; + case cProtocol::Payload::SchemaTreeNodeUnstructuredArray: + return SchemaTree::Node::Type::UnstructuredArray; + case cProtocol::Payload::SchemaTreeNodeObj: + return SchemaTree::Node::Type::Obj; + default: + return std::nullopt; + } +} + +auto deserialize_schema_tree_node_parent_id(ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result> { + encoded_tag_t tag{}; + if (auto const err{deserialize_tag(reader, tag)}; IRErrorCode::IRErrorCode_Success != err) { + return ir_error_code_to_errc(err); + } + return deserialize_and_decode_schema_tree_node_id< + cProtocol::Payload::EncodedSchemaTreeNodeParentIdByte, + cProtocol::Payload::EncodedSchemaTreeNodeParentIdShort, + cProtocol::Payload::EncodedSchemaTreeNodeParentIdInt>(tag, reader); +} + +auto deserialize_schema_tree_node_key_name(ReaderInterface& reader, std::string& key_name) + -> IRErrorCode { + encoded_tag_t str_packet_tag{}; + if (auto const err{deserialize_tag(reader, str_packet_tag)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + if (auto const err{deserialize_string(reader, str_packet_tag, key_name)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + return IRErrorCode::IRErrorCode_Success; +} + +auto deserialize_int_val(ReaderInterface& reader, encoded_tag_t tag, value_int_t& val) + -> IRErrorCode { + if (cProtocol::Payload::ValueInt8 == tag) { + int8_t deserialized_val{}; + if (false == deserialize_int(reader, deserialized_val)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + val = deserialized_val; + } else if (cProtocol::Payload::ValueInt16 == tag) { + int16_t deserialized_val{}; + if (false == deserialize_int(reader, deserialized_val)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + val = deserialized_val; + } else if (cProtocol::Payload::ValueInt32 == tag) { + int32_t deserialized_val{}; + if (false == deserialize_int(reader, deserialized_val)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + val = deserialized_val; + } else if (cProtocol::Payload::ValueInt64 == tag) { + int64_t deserialized_val{}; + if (false == deserialize_int(reader, deserialized_val)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + val = deserialized_val; + } else { + return IRErrorCode::IRErrorCode_Corrupted_IR; + } + return IRErrorCode::IRErrorCode_Success; +} + +auto deserialize_string(ReaderInterface& reader, encoded_tag_t tag, std::string& deserialized_str) + -> IRErrorCode { + size_t str_length{}; + if (cProtocol::Payload::StrLenUByte == tag) { + uint8_t length{}; + if (false == deserialize_int(reader, length)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + str_length = static_cast(length); + } else if (cProtocol::Payload::StrLenUShort == tag) { + uint16_t length{}; + if (false == deserialize_int(reader, length)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + str_length = static_cast(length); + } else if (cProtocol::Payload::StrLenUInt == tag) { + uint32_t length{}; + if (false == deserialize_int(reader, length)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + str_length = static_cast(length); + } else { + return IRErrorCode::IRErrorCode_Corrupted_IR; + } + if (clp::ErrorCode_Success != reader.try_read_string(str_length, deserialized_str)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + return IRErrorCode::IRErrorCode_Success; +} + +auto deserialize_schema(ReaderInterface& reader, encoded_tag_t& tag) + -> OUTCOME_V2_NAMESPACE::std_result { + Schema schema; + while (true) { + if (false == is_encoded_key_id_tag(tag)) { + // The log event must be an empty value. + break; + } + + auto const schema_tree_node_id_result{deserialize_and_decode_schema_tree_node_id< + cProtocol::Payload::EncodedSchemaTreeNodeIdByte, + cProtocol::Payload::EncodedSchemaTreeNodeIdShort, + cProtocol::Payload::EncodedSchemaTreeNodeIdInt>(tag, reader)}; + if (schema_tree_node_id_result.has_error()) { + return schema_tree_node_id_result.error(); + } + auto const [is_auto_generated, node_id]{schema_tree_node_id_result.value()}; + if (is_auto_generated) { + // Currently, we don't support auto-generated keys. + return std::errc::protocol_not_supported; + } + schema.push_back(node_id); + + if (auto const err{deserialize_tag(reader, tag)}; IRErrorCode::IRErrorCode_Success != err) { + return ir_error_code_to_errc(err); + } + } + + return schema; +} + +auto deserialize_value_and_insert_to_node_id_value_pairs( + ReaderInterface& reader, + encoded_tag_t tag, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode { + switch (tag) { + case cProtocol::Payload::ValueInt8: + case cProtocol::Payload::ValueInt16: + case cProtocol::Payload::ValueInt32: + case cProtocol::Payload::ValueInt64: { + value_int_t value_int{}; + if (auto const err{deserialize_int_val(reader, tag, value_int)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + node_id_value_pairs.emplace(node_id, Value{value_int}); + break; + } + case cProtocol::Payload::ValueFloat: { + uint64_t val{}; + if (false == deserialize_int(reader, val)) { + return IRErrorCode::IRErrorCode_Incomplete_IR; + } + node_id_value_pairs.emplace(node_id, Value{bit_cast(val)}); + break; + } + case cProtocol::Payload::ValueTrue: + node_id_value_pairs.emplace(node_id, Value{true}); + break; + case cProtocol::Payload::ValueFalse: + node_id_value_pairs.emplace(node_id, Value{false}); + break; + case cProtocol::Payload::StrLenUByte: + case cProtocol::Payload::StrLenUShort: + case cProtocol::Payload::StrLenUInt: { + std::string value_str; + if (auto const err{deserialize_string(reader, tag, value_str)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + node_id_value_pairs.emplace(node_id, Value{std::move(value_str)}); + break; + } + case cProtocol::Payload::ValueEightByteEncodingClpStr: + if (auto const err{deserialize_encoded_text_ast_and_insert_to_node_id_value_pairs< + ir::eight_byte_encoded_variable_t>(reader, node_id, node_id_value_pairs)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + break; + case cProtocol::Payload::ValueFourByteEncodingClpStr: + if (auto const err{deserialize_encoded_text_ast_and_insert_to_node_id_value_pairs< + ir::four_byte_encoded_variable_t>(reader, node_id, node_id_value_pairs)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + break; + case cProtocol::Payload::ValueNull: + node_id_value_pairs.emplace(node_id, Value{}); + break; + case cProtocol::Payload::ValueEmpty: + node_id_value_pairs.emplace(node_id, std::nullopt); + break; + default: + return IRErrorCode::IRErrorCode_Corrupted_IR; + } + return IRErrorCode::IRErrorCode_Success; +} + +template +requires(std::is_same_v + || std::is_same_v) +[[nodiscard]] auto deserialize_encoded_text_ast_and_insert_to_node_id_value_pairs( + ReaderInterface& reader, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode { + encoded_tag_t tag{}; + if (auto const err{deserialize_tag(reader, tag)}; IRErrorCode::IRErrorCode_Success != err) { + return err; + } + + std::string logtype; + std::vector encoded_vars; + std::vector dict_vars; + if (auto const err{deserialize_encoded_text_ast(reader, tag, logtype, encoded_vars, dict_vars)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + + node_id_value_pairs.emplace( + node_id, + Value{ir::EncodedTextAst{logtype, dict_vars, encoded_vars}} + ); + return IRErrorCode::IRErrorCode_Success; +} + +auto deserialize_value_and_construct_node_id_value_pairs( + ReaderInterface& reader, + encoded_tag_t tag, + Schema const& schema, + KeyValuePairLogEvent::NodeIdValuePairs& node_id_value_pairs +) -> IRErrorCode { + node_id_value_pairs.clear(); + node_id_value_pairs.reserve(schema.size()); + for (auto const node_id : schema) { + if (node_id_value_pairs.contains(node_id)) { + // The key should be unique in a schema + return IRErrorCode_Corrupted_IR; + } + + if (auto const err{deserialize_value_and_insert_to_node_id_value_pairs( + reader, + tag, + node_id, + node_id_value_pairs + )}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + + if (schema.size() != node_id_value_pairs.size()) { + if (auto const err{deserialize_tag(reader, tag)}; + IRErrorCode::IRErrorCode_Success != err) + { + return err; + } + } + } + return IRErrorCode::IRErrorCode_Success; +} + +auto is_log_event_ir_unit_tag(encoded_tag_t tag) -> bool { + if (cProtocol::Payload::ValueEmpty == tag) { + // The log event is an empty object + return true; + } + if (is_encoded_key_id_tag(tag)) { + // If not empty, the log event must start with a tag byte indicating the key ID + return true; + } + return false; +} + +auto is_encoded_key_id_tag(encoded_tag_t tag) -> bool { + // Ideally, we could check whether the tag is within the range of + // [EncodedKeyIdByte, EncodedKeyIdInt], but we don't for two reasons: + // - We optimize for streams that have few key IDs, meaning we can short circuit in the first + // branch below. + // - Using a range check assumes all length indicators are defined continuously, in order, but + // we don't have static checks for this assumption. + return cProtocol::Payload::EncodedSchemaTreeNodeIdByte == tag + || cProtocol::Payload::EncodedSchemaTreeNodeIdShort == tag + || cProtocol::Payload::EncodedSchemaTreeNodeIdInt == tag; +} +} // namespace + +auto get_ir_unit_type_from_tag(encoded_tag_t tag) -> std::optional { + // First, we check the tags that have one-to-one IR unit mapping + if (cProtocol::Eof == tag) { + return IrUnitType::EndOfStream; + } + if (cProtocol::Payload::UtcOffsetChange == tag) { + return IrUnitType::UtcOffsetChange; + } + + // Then, check tags that may match any byte within a continuous range + if ((tag & cProtocol::Payload::SchemaTreeNodeMask) == cProtocol::Payload::SchemaTreeNodeMask) { + return IrUnitType::SchemaTreeNodeInsertion; + } + + if (is_log_event_ir_unit_tag(tag)) { + return IrUnitType::LogEvent; + } + + return std::nullopt; +} + +auto deserialize_ir_unit_schema_tree_node_insertion( + ReaderInterface& reader, + encoded_tag_t tag, + std::string& key_name +) -> OUTCOME_V2_NAMESPACE::std_result { + auto const type{schema_tree_node_tag_to_type(tag)}; + if (false == type.has_value()) { + return ir_error_code_to_errc(IRErrorCode::IRErrorCode_Corrupted_IR); + } + + auto const parent_node_id_result{deserialize_schema_tree_node_parent_id(reader)}; + if (parent_node_id_result.has_error()) { + return parent_node_id_result.error(); + } + auto const [is_auto_generated, parent_id]{parent_node_id_result.value()}; + if (is_auto_generated) { + // Currently, we don't support auto-generated keys. + return std::errc::protocol_not_supported; + } + + if (auto const err{deserialize_schema_tree_node_key_name(reader, key_name)}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + + return SchemaTree::NodeLocator{parent_id, key_name, type.value()}; +} + +auto deserialize_ir_unit_utc_offset_change(ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result { + UtcOffset utc_offset{0}; + if (auto const err{deserialize_utc_offset_change(reader, utc_offset)}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + return utc_offset; +} + +auto deserialize_ir_unit_kv_pair_log_event( + ReaderInterface& reader, + encoded_tag_t tag, + std::shared_ptr schema_tree, + UtcOffset utc_offset +) -> OUTCOME_V2_NAMESPACE::std_result { + auto const schema_result{deserialize_schema(reader, tag)}; + if (schema_result.has_error()) { + return schema_result.error(); + } + auto const& schema{schema_result.value()}; + + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + if (false == schema.empty()) { + if (auto const err{deserialize_value_and_construct_node_id_value_pairs( + reader, + tag, + schema, + node_id_value_pairs + )}; + IRErrorCode::IRErrorCode_Success != err) + { + return ir_error_code_to_errc(err); + } + } else { + if (cProtocol::Payload::ValueEmpty != tag) { + return ir_error_code_to_errc(IRErrorCode::IRErrorCode_Corrupted_IR); + } + } + + return KeyValuePairLogEvent::create( + std::move(schema_tree), + std::move(node_id_value_pairs), + utc_offset + ); +} +} // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp new file mode 100644 index 000000000..68ed4408b --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/ir_unit_deserialization_methods.hpp @@ -0,0 +1,80 @@ +#ifndef CLP_FFI_IR_STREAM_IR_UNIT_DESERIALIZATION_METHODS_HPP +#define CLP_FFI_IR_STREAM_IR_UNIT_DESERIALIZATION_METHODS_HPP + +#include +#include +#include + +#include + +#include "../../ReaderInterface.hpp" +#include "../../time_types.hpp" +#include "../KeyValuePairLogEvent.hpp" +#include "../SchemaTree.hpp" +#include "decoding_methods.hpp" +#include "IrUnitType.hpp" + +namespace clp::ffi::ir_stream { +/** + * @param tag + * @return The IR unit type of indicated by the given tag on success. + * @return std::nullopt if the tag doesn't represent a valid IR unit. + */ +[[nodiscard]] auto get_ir_unit_type_from_tag(encoded_tag_t tag) -> std::optional; + +/** + * Deserializes a schema tree node insertion IR unit. + * @param reader + * @param tag + * @param key_name Returns the key name of the deserialized new node. This should be the underlying + * storage of the returned schema tree node locator. + * @return A result containing the locator of the inserted schema tree node or an error code + * indicating the failure: + * - std::errc::result_out_of_range if the IR stream is truncated. + * - std::errc::protocol_error if the deserialized node type isn't supported. + * - std::errc::protocol_not_supported if the IR stream contains auto-generated keys (TODO: Remove + * this once auto-generated keys are fully supported). + * - Forwards `deserialize_schema_tree_node_key_name`'s return values. + * - Forwards `deserialize_schema_tree_node_parent_id`'s return values. + */ +[[nodiscard]] auto deserialize_ir_unit_schema_tree_node_insertion( + ReaderInterface& reader, + encoded_tag_t tag, + std::string& key_name +) -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * Deserializes a UTC offset change IR unit. + * @param reader + * @return A result containing the new UTC offset or an error code indicating the failure: + * - std::errc::result_out_of_range if the IR stream is truncated. + * - Forwards `clp::ffi::ir_stream::deserialize_utc_offset_change`'s return values. + */ +[[nodiscard]] auto deserialize_ir_unit_utc_offset_change(ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * Deserializes a key-value pair log event IR unit. + * @param reader + * @param tag + * @param schema_tree Schema tree used to construct the KV-pair log event. + * @param utc_offset UTC offset used to construct the KV-pair log event. + * @return A result containing the deserialized log event or an error code indicating the + * failure: + * - std::errc::result_out_of_range if the IR stream is truncated. + * - std::errc::protocol_error if the IR stream is corrupted. + * - std::errc::protocol_not_supported if the IR stream contains an unsupported metadata format + * or uses an unsupported version. + * - Forwards `deserialize_schema`'s return values. + * - Forwards `KeyValuePairLogEvent::create`'s return values if the intermediate deserialized result + * cannot construct a valid key-value pair log event. + */ +[[nodiscard]] auto deserialize_ir_unit_kv_pair_log_event( + ReaderInterface& reader, + encoded_tag_t tag, + std::shared_ptr schema_tree, + UtcOffset utc_offset +) -> OUTCOME_V2_NAMESPACE::std_result; +} // namespace clp::ffi::ir_stream + +#endif // CLP_FFI_IR_STREAM_IR_UNIT_DESERIALIZATION_METHODS_HPP diff --git a/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp b/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp index d442209de..d89b99cf5 100644 --- a/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp +++ b/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace clp::ffi::ir_stream::cProtocol { @@ -12,7 +13,10 @@ constexpr int8_t LengthUByte = 0x11; constexpr int8_t LengthUShort = 0x12; constexpr char VersionKey[] = "VERSION"; -constexpr char VersionValue[] = "0.0.2"; +constexpr std::string_view VersionValue{"0.1.0"}; + +// This is used for the IR stream format that predates the key-value pair IR format. +constexpr std::string_view LatestBackwardCompatibleVersion{"0.0.2"}; // The following regex can be used to validate a Semantic Versioning string. The source of the // regex can be found here: https://semver.org/ @@ -49,6 +53,39 @@ constexpr int8_t TimestampDeltaInt = 0x33; constexpr int8_t TimestampDeltaLong = 0x34; constexpr int8_t UtcOffsetChange = 0x3F; + +constexpr int8_t StrLenUByte = 0x41; +constexpr int8_t StrLenUShort = 0x42; +constexpr int8_t StrLenUInt = 0x43; + +constexpr int8_t ValueInt8 = 0x51; +constexpr int8_t ValueInt16 = 0x52; +constexpr int8_t ValueInt32 = 0x53; +constexpr int8_t ValueInt64 = 0x54; +constexpr int8_t ValueFloat = 0x56; +constexpr int8_t ValueTrue = 0x57; +constexpr int8_t ValueFalse = 0x58; +constexpr int8_t ValueFourByteEncodingClpStr = 0x59; +constexpr int8_t ValueEightByteEncodingClpStr = 0x5A; +constexpr int8_t ValueEmpty = 0x5E; +constexpr int8_t ValueNull = 0x5F; + +constexpr int8_t EncodedSchemaTreeNodeParentIdByte = 0x60; +constexpr int8_t EncodedSchemaTreeNodeParentIdShort = 0x61; +constexpr int8_t EncodedSchemaTreeNodeParentIdInt = 0x62; + +constexpr int8_t EncodedSchemaTreeNodeIdByte = 0x65; +constexpr int8_t EncodedSchemaTreeNodeIdShort = 0x66; +constexpr int8_t EncodedSchemaTreeNodeIdInt = 0x67; + +constexpr int8_t SchemaTreeNodeMask = 0x70; + +constexpr int8_t SchemaTreeNodeInt = 0x71; +constexpr int8_t SchemaTreeNodeFloat = 0x72; +constexpr int8_t SchemaTreeNodeBool = 0x73; +constexpr int8_t SchemaTreeNodeStr = 0x74; +constexpr int8_t SchemaTreeNodeUnstructuredArray = 0x75; +constexpr int8_t SchemaTreeNodeObj = 0x76; } // namespace Payload constexpr int8_t FourByteEncodingMagicNumber[] diff --git a/components/core/src/clp/ffi/ir_stream/utils.cpp b/components/core/src/clp/ffi/ir_stream/utils.cpp new file mode 100644 index 000000000..371d7cfba --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/utils.cpp @@ -0,0 +1,70 @@ +#include "utils.hpp" + +#include +#include +#include +#include + +#include + +#include "../../type_utils.hpp" +#include "decoding_methods.hpp" +#include "protocol_constants.hpp" + +namespace clp::ffi::ir_stream { +auto serialize_metadata(nlohmann::json& metadata, std::vector& output_buf) -> bool { + output_buf.push_back(cProtocol::Metadata::EncodingJson); + + auto const metadata_serialized + = metadata.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore); + auto const metadata_serialized_length = metadata_serialized.length(); + if (metadata_serialized_length <= UINT8_MAX) { + output_buf.push_back(cProtocol::Metadata::LengthUByte); + output_buf.push_back(bit_cast(static_cast(metadata_serialized_length))); + } else if (metadata_serialized_length <= UINT16_MAX) { + output_buf.push_back(cProtocol::Metadata::LengthUShort); + serialize_int(static_cast(metadata_serialized_length), output_buf); + } else { + // Can't encode metadata longer than 64 KiB + return false; + } + output_buf.insert(output_buf.cend(), metadata_serialized.cbegin(), metadata_serialized.cend()); + + return true; +} + +auto serialize_string(std::string_view str, std::vector& output_buf) -> bool { + auto const length{str.length()}; + if (length <= UINT8_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUByte); + output_buf.push_back(bit_cast(static_cast(length))); + } else if (length <= UINT16_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUShort); + serialize_int(static_cast(length), output_buf); + } else if (length <= UINT32_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUInt); + serialize_int(static_cast(length), output_buf); + } else { + // Out of range + return false; + } + output_buf.insert(output_buf.cend(), str.cbegin(), str.cend()); + return true; +} + +auto ir_error_code_to_errc(IRErrorCode ir_error_code) -> std::errc { + switch (ir_error_code) { + case IRErrorCode_Success: + return {}; + case IRErrorCode_Incomplete_IR: + return std::errc::result_out_of_range; + case IRErrorCode_Corrupted_IR: + case IRErrorCode_Decode_Error: + return std::errc::protocol_error; + case IRErrorCode_Eof: + return std::errc::no_message_available; + default: + return std::errc::not_supported; + } +} +} // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/utils.hpp b/components/core/src/clp/ffi/ir_stream/utils.hpp new file mode 100644 index 000000000..3e545c4fe --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/utils.hpp @@ -0,0 +1,274 @@ +#ifndef CLP_FFI_IR_STREAM_UTILS_HPP +#define CLP_FFI_IR_STREAM_UTILS_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../ErrorCode.hpp" +#include "../../ir/types.hpp" +#include "../../ReaderInterface.hpp" +#include "../../type_utils.hpp" +#include "../SchemaTree.hpp" +#include "byteswap.hpp" +#include "decoding_methods.hpp" +#include "encoding_methods.hpp" +#include "protocol_constants.hpp" + +namespace clp::ffi::ir_stream { +/** + * Serializes the given metadata into the IR stream. + * @param metadata + * @param output_buf + * @return Whether serialization succeeded. + */ +[[nodiscard]] auto +serialize_metadata(nlohmann::json& metadata, std::vector& output_buf) -> bool; + +/** + * Serializes the given integer into the IR stream. + * @tparam integer_t + * @param value + * @param output_buf + */ +template +auto serialize_int(integer_t value, std::vector& output_buf) -> void; + +/** + * Deserializes an integer from the given reader + * @tparam integer_t Type of the integer to deserialize + * @param reader + * @param value Returns the deserialized integer + * @return Whether the reader contained enough data to deserialize. + */ +template +[[nodiscard]] auto deserialize_int(ReaderInterface& reader, integer_t& value) -> bool; + +/** + * Serializes a string using CLP's encoding for unstructured text. + * @tparam encoded_variable_t + * @param str + * @param logtype Returns the corresponding logtype. + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto serialize_clp_string( + std::string_view str, + std::string& logtype, + std::vector& output_buf +) -> bool; + +/** + * Serializes a string. + * @param str + * @param output_buf + * @return Whether serialization succeeded. + */ +[[nodiscard]] auto serialize_string(std::string_view str, std::vector& output_buf) -> bool; + +/** + * @tparam T + * @param int_val + * @return One's complement of `int_val`. + */ +template +[[nodiscard]] auto get_ones_complement(T int_val) -> T; + +/** + * Encodes and serializes a schema tree node ID. + * @tparam is_auto_generated_node Whether the schema tree node ID is from the auto-generated or the + * user-generated schema tree. + * @tparam one_byte_length_indicator_tag Tag for one-byte node ID encoding. + * @tparam two_byte_length_indicator_tag Tag for two-byte node ID encoding. + * @tparam four_byte_length_indicator_tag Tag for four-byte node ID encoding. + * @param node_id + * @param output_buf + * @return true on success. + * @return false if the ID exceeds the representable range. + */ +template < + bool is_auto_generated_node, + int8_t one_byte_length_indicator_tag, + int8_t two_byte_length_indicator_tag, + int8_t four_byte_length_indicator_tag> +[[nodiscard]] auto encode_and_serialize_schema_tree_node_id( + SchemaTree::Node::id_t node_id, + std::vector& output_buf +) -> bool; + +/** + * Deserializes and decodes a schema tree node ID. + * @tparam one_byte_length_indicator_tag Tag for one-byte node ID encoding. + * @tparam two_byte_length_indicator_tag Tag for two-byte node ID encoding. + * @tparam four_byte_length_indicator_tag Tag for four-byte node ID encoding. + * @param length_indicator_tag + * @param reader + * @return A result containing a pair or an error code indicating the failure: + * - The pair: + * - Whether the node ID is for an auto-generated node. + * - The decoded node ID. + * - The possible error codes: + * - std::errc::protocol_error if the given length indicator is unknown. + * @return Forwards `size_dependent_deserialize_and_decode_schema_tree_node_id`'s return values. + */ +template < + int8_t one_byte_length_indicator_tag, + int8_t two_byte_length_indicator_tag, + int8_t four_byte_length_indicator_tag> +[[nodiscard]] auto deserialize_and_decode_schema_tree_node_id( + encoded_tag_t length_indicator_tag, + ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result>; + +/** + * @param ir_error_code + * @return Equivalent `std::errc` code indicating the same error type. + */ +[[nodiscard]] auto ir_error_code_to_errc(IRErrorCode ir_error_code) -> std::errc; + +template +auto serialize_int(integer_t value, std::vector& output_buf) -> void { + integer_t value_big_endian{}; + if constexpr (sizeof(value) == 1) { + output_buf.push_back(bit_cast(value)); + return; + } else if constexpr (sizeof(value) == 2) { + value_big_endian = bswap_16(value); + } else if constexpr (sizeof(value) == 4) { + value_big_endian = bswap_32(value); + } else if constexpr (sizeof(value) == 8) { + value_big_endian = bswap_64(value); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + std::span const data_view{reinterpret_cast(&value_big_endian), sizeof(value)}; + output_buf.insert(output_buf.end(), data_view.begin(), data_view.end()); +} + +template +auto deserialize_int(ReaderInterface& reader, integer_t& value) -> bool { + integer_t value_little_endian; + if (reader.try_read_numeric_value(value_little_endian) != clp::ErrorCode_Success) { + return false; + } + + constexpr auto cReadSize = sizeof(integer_t); + if constexpr (cReadSize == 1) { + value = value_little_endian; + } else if constexpr (cReadSize == 2) { + value = bswap_16(value_little_endian); + } else if constexpr (cReadSize == 4) { + value = bswap_32(value_little_endian); + } else if constexpr (cReadSize == 8) { + value = bswap_64(value_little_endian); + } + return true; +} + +template +[[nodiscard]] auto serialize_clp_string( + std::string_view str, + std::string& logtype, + std::vector& output_buf +) -> bool { + static_assert( + (std::is_same_v + || std::is_same_v) + ); + bool succeeded{}; + if constexpr (std::is_same_v) { + output_buf.push_back(cProtocol::Payload::ValueFourByteEncodingClpStr); + succeeded = four_byte_encoding::serialize_message(str, logtype, output_buf); + } else { + output_buf.push_back(cProtocol::Payload::ValueEightByteEncodingClpStr); + succeeded = eight_byte_encoding::serialize_message(str, logtype, output_buf); + } + return succeeded; +} + +template +auto get_ones_complement(T int_val) -> T { + // Explicit cast to undo the implicit integer promotion + return static_cast(~int_val); +} + +template < + bool is_auto_generated_node, + int8_t one_byte_length_indicator_tag, + int8_t two_byte_length_indicator_tag, + int8_t four_byte_length_indicator_tag> +auto encode_and_serialize_schema_tree_node_id( + SchemaTree::Node::id_t node_id, + std::vector& output_buf +) -> bool { + auto size_dependent_encode_and_serialize_schema_tree_node_id + = [&output_buf, + &node_id](int8_t length_indicator_tag) -> void { + output_buf.push_back(length_indicator_tag); + if constexpr (is_auto_generated_node) { + serialize_int(get_ones_complement(static_cast(node_id)), output_buf); + } else { + serialize_int(static_cast(node_id), output_buf); + } + }; + + if (node_id <= static_cast(INT8_MAX)) { + size_dependent_encode_and_serialize_schema_tree_node_id.template operator( + )(one_byte_length_indicator_tag); + } else if (node_id <= static_cast(INT16_MAX)) { + size_dependent_encode_and_serialize_schema_tree_node_id.template operator( + )(two_byte_length_indicator_tag); + } else if (node_id <= static_cast(INT32_MAX)) { + size_dependent_encode_and_serialize_schema_tree_node_id.template operator( + )(four_byte_length_indicator_tag); + } else { + return false; + } + return true; +} + +template < + int8_t one_byte_length_indicator_tag, + int8_t two_byte_length_indicator_tag, + int8_t four_byte_length_indicator_tag> +auto deserialize_and_decode_schema_tree_node_id( + encoded_tag_t length_indicator_tag, + ReaderInterface& reader +) -> OUTCOME_V2_NAMESPACE::std_result> { + auto size_dependent_deserialize_and_decode_schema_tree_node_id + = [&reader]( + ) -> OUTCOME_V2_NAMESPACE::std_result> { + encoded_node_id_t encoded_node_id{}; + if (false == deserialize_int(reader, encoded_node_id)) { + return std::errc::result_out_of_range; + } + if (0 > encoded_node_id) { + return {true, static_cast(get_ones_complement(encoded_node_id)) + }; + } + return {false, static_cast(encoded_node_id)}; + }; + + if (one_byte_length_indicator_tag == length_indicator_tag) { + return size_dependent_deserialize_and_decode_schema_tree_node_id.template operator( + )(); + } + if (two_byte_length_indicator_tag == length_indicator_tag) { + return size_dependent_deserialize_and_decode_schema_tree_node_id.template operator( + )(); + } + if (four_byte_length_indicator_tag == length_indicator_tag) { + return size_dependent_deserialize_and_decode_schema_tree_node_id.template operator( + )(); + } + return std::errc::protocol_error; +} +} // namespace clp::ffi::ir_stream +#endif diff --git a/components/core/src/clp/ffi/utils.cpp b/components/core/src/clp/ffi/utils.cpp new file mode 100644 index 000000000..c85c47701 --- /dev/null +++ b/components/core/src/clp/ffi/utils.cpp @@ -0,0 +1,89 @@ +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../utf8_utils.hpp" + +using std::string; +using std::string_view; + +namespace clp::ffi { +auto validate_and_escape_utf8_string(string_view raw) -> std::optional { + std::optional ret_val; + auto& escaped{ret_val.emplace()}; + escaped.reserve(raw.size() + (raw.size() / 2)); + if (false == validate_and_append_escaped_utf8_string(raw, escaped)) { + return std::nullopt; + } + return ret_val; +} + +auto validate_and_append_escaped_utf8_string(std::string_view src, std::string& dst) -> bool { + string_view::const_iterator next_char_to_copy_it{src.cbegin()}; + + auto escape_handler = [&](string_view::const_iterator it) -> void { + // Allocate 6 + 1 size buffer to format control characters as "\u00bb", with the last byte + // used by `snprintf` to append '\0' + constexpr size_t cControlCharacterBufSize{7}; + std::array buf{}; + std::string_view escaped_char; + bool escape_required{true}; + switch (*it) { + case '\b': + escaped_char = "\\b"; + break; + case '\t': + escaped_char = "\\t"; + break; + case '\n': + escaped_char = "\\n"; + break; + case '\f': + escaped_char = "\\f"; + break; + case '\r': + escaped_char = "\\r"; + break; + case '\\': + escaped_char = "\\\\"; + break; + case '"': + escaped_char = "\\\""; + break; + default: { + constexpr uint8_t cLargestControlCharacter{0x1F}; + auto const byte{static_cast(*it)}; + if (cLargestControlCharacter >= byte) { + std::ignore = snprintf(buf.data(), buf.size(), "\\u00%02x", byte); + escaped_char = {buf.data(), buf.size() - 1}; + } else { + escape_required = false; + } + break; + } + } + if (escape_required) { + dst.append(next_char_to_copy_it, it); + dst += escaped_char; + next_char_to_copy_it = it + 1; + } + }; + + if (false == validate_utf8_string(src, escape_handler)) { + return false; + } + + if (src.cend() != next_char_to_copy_it) { + dst.append(next_char_to_copy_it, src.cend()); + } + + return true; +} +} // namespace clp::ffi diff --git a/components/core/src/clp/ffi/utils.hpp b/components/core/src/clp/ffi/utils.hpp new file mode 100644 index 000000000..26823da9c --- /dev/null +++ b/components/core/src/clp/ffi/utils.hpp @@ -0,0 +1,31 @@ +#ifndef CLP_FFI_UTILS_HPP +#define CLP_FFI_UTILS_HPP + +#include +#include +#include + +namespace clp::ffi { +/** + * Validates whether the given string is UTF-8 encoded, and escapes any characters to make the + * string compatible with the JSON specification. + * @param raw The raw string to escape. + * @return The escaped string on success. + * @return std::nullopt if the string contains any non-UTF-8-encoded byte sequences. + */ +[[nodiscard]] auto validate_and_escape_utf8_string(std::string_view raw +) -> std::optional; + +/** + * Validates whether `src` is UTF-8 encoded, and appends `src` to `dst` while escaping any + * characters to make the appended string compatible with the JSON specification. + * @param src The string to validate and escape. + * @param dst Returns `dst` with an escaped version of `src` appended. + * @return Whether `src` is a valid UTF-8-encoded string. NOTE: Even if `src` is not UTF-8 encoded, + * `dst` may be modified. + */ +[[nodiscard]] auto +validate_and_append_escaped_utf8_string(std::string_view src, std::string& dst) -> bool; +} // namespace clp::ffi + +#endif // CLP_FFI_UTILS_HPP diff --git a/components/core/src/clp/hash_utils.cpp b/components/core/src/clp/hash_utils.cpp new file mode 100644 index 000000000..a70cd8ff6 --- /dev/null +++ b/components/core/src/clp/hash_utils.cpp @@ -0,0 +1,223 @@ +#include "hash_utils.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +using std::make_unique; +using std::span; +using std::string; +using std::unique_ptr; +using std::vector; + +namespace clp { +namespace { +/** + * Pops the first OpenSSL error from its error queue and gets its string representation. + * @return The string representing the first OpenSSL error from its error queue. + */ +auto get_openssl_error_string() -> string { + auto const openssl_err = ERR_get_error(); + if (0 == openssl_err) { + return {}; + } + auto* openssl_err_str = ERR_error_string(openssl_err, nullptr); + if (nullptr == openssl_err_str) { + return {"Error has no string representation, error_code: " + std::to_string(openssl_err)}; + } + return {openssl_err_str}; +} + +/** + * A C++ wrapper for OpenSSL's EVP message digest context (EVP_MD_CTX). + */ +class EvpDigestContext { +public: + // Types + class OperationFailed : public clp::TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : OperationFailed( + error_code, + filename, + line_number, + "EvpDigestContext operation failed" + ) {} + + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + string message + ) + : TraceableException(error_code, filename, line_number), + m_message(std::move(message)) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { + return m_message.c_str(); + } + + private: + string m_message; + }; + + // Constructors + /** + * @param type The type of digest (hash algorithm). + * @throw EvpDigestContext::OperationFailed with ErrorCode_NoMem if `EVP_MD_CTX_create` fails. + * @throw EvpDigestContext::OperationFailed with ErrorCode_Failure if `EVP_DigestInit_ex` fails. + */ + explicit EvpDigestContext(EVP_MD const* type) + : m_md_ctx{EVP_MD_CTX_create()}, + m_digest_nid{EVP_MD_type(type)} { + if (nullptr == m_md_ctx) { + throw OperationFailed(ErrorCode_NoMem, __FILENAME__, __LINE__); + } + // Set impl to nullptr to use the default implementation of digest type + if (1 != EVP_DigestInit_ex(m_md_ctx, type, nullptr)) { + throw OperationFailed( + ErrorCode_Failure, + __FILENAME__, + __LINE__, + get_openssl_error_string() + ); + } + } + + // Disable copy constructor and assignment operator + EvpDigestContext(EvpDigestContext const&) = delete; + auto operator=(EvpDigestContext const&) -> EvpDigestContext& = delete; + + // Disable move constructor and assignment operator + EvpDigestContext(EvpDigestContext&&) = delete; + auto operator=(EvpDigestContext&&) -> EvpDigestContext& = delete; + + // Destructor + ~EvpDigestContext() { EVP_MD_CTX_destroy(m_md_ctx); } + + // Methods + /** + * Hashes `input` into the digest. + * @param input + * @return Whether `EVP_DigestUpdate` succeeded. + */ + [[nodiscard]] auto digest_update(span input) -> bool; + + /** + * Writes the digest into `hash` and clears the digest. + * @param hash Returns the hashing result. + * @return ErrorCode_Success on success. + * @return ErrorCode_Corrupt if `hash` has an unexpected length. + * @return ErrorCode_Failure if `EVP_DigestFinal_ex` fails. + * @throw EvpDigestContext::OperationFailed with ErrorCode_Failure if `EVP_DigestInit_ex` fails. + */ + [[nodiscard]] auto digest_final(vector& hash) -> ErrorCode; + +private: + EVP_MD_CTX* m_md_ctx{nullptr}; + int m_digest_nid{}; +}; + +auto EvpDigestContext::digest_update(span input) -> bool { + if (1 != EVP_DigestUpdate(m_md_ctx, input.data(), input.size())) { + return false; + } + return true; +} + +auto EvpDigestContext::digest_final(vector& hash) -> ErrorCode { + hash.resize(EVP_MD_CTX_size(m_md_ctx)); + unsigned int length{}; + if (1 != EVP_DigestFinal_ex(m_md_ctx, hash.data(), &length)) { + return ErrorCode_Failure; + } + if (hash.size() != length) { + return ErrorCode_Corrupt; + } + + if (1 != EVP_DigestInit_ex(m_md_ctx, EVP_get_digestbynid(m_digest_nid), nullptr)) { + throw OperationFailed( + ErrorCode_Failure, + __FILENAME__, + __LINE__, + get_openssl_error_string() + ); + } + return ErrorCode_Success; +} +} // namespace + +auto convert_to_hex_string(span input) -> string { + string hex_string; + for (auto const c : input) { + hex_string += fmt::format("{:02x}", c); + } + return hex_string; +} + +auto get_hmac_sha256_hash( + span input, + span key, + vector& hash +) -> ErrorCode { + if (key.size() > INT32_MAX) { + return ErrorCode_BadParam; + } + + hash.resize(SHA256_DIGEST_LENGTH); + unsigned int hash_length{0}; + auto const key_length{static_cast(key.size())}; + if (nullptr + == HMAC(EVP_sha256(), + key.data(), + key_length, + input.data(), + input.size(), + hash.data(), + &hash_length)) + { + return ErrorCode_Failure; + } + + if (hash.size() != hash_length) { + return ErrorCode_Corrupt; + } + + return ErrorCode_Success; +} + +auto get_sha256_hash(span input, vector& hash) -> ErrorCode { + unique_ptr evp_ctx_manager; + try { + evp_ctx_manager = make_unique(EVP_sha256()); + } catch (EvpDigestContext::OperationFailed const& err) { + throw HashUtilsOperationFailed(err.get_error_code(), __FILENAME__, __LINE__, err.what()); + } + + if (false == evp_ctx_manager->digest_update(input)) { + return ErrorCode_Failure; + } + + if (auto const error_code = evp_ctx_manager->digest_final(hash); + ErrorCode_Success != error_code) + { + return error_code; + } + + return ErrorCode_Success; +} +} // namespace clp diff --git a/components/core/src/clp/hash_utils.hpp b/components/core/src/clp/hash_utils.hpp new file mode 100644 index 000000000..4606aed23 --- /dev/null +++ b/components/core/src/clp/hash_utils.hpp @@ -0,0 +1,77 @@ +#ifndef CLP_HASH_UTILS_HPP +#define CLP_HASH_UTILS_HPP + +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +// Types +class HashUtilsOperationFailed : public TraceableException { +public: + // Constructors + HashUtilsOperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : HashUtilsOperationFailed( + error_code, + filename, + line_number, + "clp::hash_utils operation failed" + ) {} + + HashUtilsOperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string message + ) + : TraceableException(error_code, filename, line_number), + m_message(std::move(message)) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { return m_message.c_str(); } + +private: + std::string m_message; +}; + +/** + * @param input + * @return `input` as a hex string (without the "0x" prefix). + */ +[[nodiscard]] auto convert_to_hex_string(std::span input) -> std::string; + +/** + * Gets the HMAC-SHA256 hash of `input` with `key`. + * @param input + * @param key + * @param hash Returns the HMAC. + * @return ErrorCode_Success on success. + * @return ErrorCode_BadParam if `key` is longer than `INT32_MAX`. + * @return ErrorCode_Failure if hash generation fails. + * @return ErrorCode_Corrupt if `hash` has an unexpected length. + */ +[[nodiscard]] auto get_hmac_sha256_hash( + std::span input, + std::span key, + std::vector& hash +) -> ErrorCode; + +/** + * Gets the SHA256 hash of `input`. + * @param input + * @param hash Returns the hash. + * @return ErrorCode_Success on success. + * @return ErrorCode_Failure if `EvpDigestContext::digest_update` fails. + * @return Same as `EvpDigestContext::digest_final` if `EvpDigestContext::digest_final` fails. + * @throw HashUtilsOperationFailed if an OpenSSL EVP digest couldn't be created. + */ +[[nodiscard]] auto get_sha256_hash( + std::span input, + std::vector& hash +) -> ErrorCode; +} // namespace clp +#endif // CLP_HASH_UTILS_HPP diff --git a/components/core/src/clp/ir/EncodedTextAst.cpp b/components/core/src/clp/ir/EncodedTextAst.cpp new file mode 100644 index 000000000..f0ee4d493 --- /dev/null +++ b/components/core/src/clp/ir/EncodedTextAst.cpp @@ -0,0 +1,57 @@ +#include "EncodedTextAst.hpp" + +#include +#include +#include + +#include "../ffi/encoding_methods.hpp" +#include "ffi/ir_stream/decoding_methods.hpp" + +using clp::ffi::decode_float_var; +using clp::ffi::decode_integer_var; +using clp::ffi::ir_stream::DecodingException; +using clp::ffi::ir_stream::generic_decode_message; +using std::optional; +using std::string; + +namespace clp::ir { +template +auto EncodedTextAst::decode_and_unparse() const -> optional { + string decoded_string; + + auto constant_handler = [&](string const& value, size_t begin_pos, size_t length) { + decoded_string.append(value, begin_pos, length); + }; + + auto encoded_int_handler + = [&](encoded_variable_t value) { decoded_string.append(decode_integer_var(value)); }; + + auto encoded_float_handler = [&](encoded_variable_t encoded_float) { + decoded_string.append(decode_float_var(encoded_float)); + }; + + auto dict_var_handler = [&](string const& dict_var) { decoded_string.append(dict_var); }; + + try { + generic_decode_message( + m_logtype, + m_encoded_vars, + m_dict_vars, + constant_handler, + encoded_int_handler, + encoded_float_handler, + dict_var_handler + ); + } catch (DecodingException const& e) { + return std::nullopt; + } + return std::make_optional(decoded_string); +} + +// Explicitly declare template specializations so that we can define the template methods in this +// file +template auto EncodedTextAst::decode_and_unparse( +) const -> optional; +template auto EncodedTextAst::decode_and_unparse( +) const -> optional; +} // namespace clp::ir diff --git a/components/core/src/clp/ir/EncodedTextAst.hpp b/components/core/src/clp/ir/EncodedTextAst.hpp new file mode 100644 index 000000000..e18759780 --- /dev/null +++ b/components/core/src/clp/ir/EncodedTextAst.hpp @@ -0,0 +1,60 @@ +#ifndef CLP_IR_ENCODEDTEXTAST_HPP +#define CLP_IR_ENCODEDTEXTAST_HPP + +#include +#include +#include +#include + +#include "types.hpp" + +namespace clp::ir { +/** + * A parsed and encoded unstructured text string. + * @tparam encoded_variable_t The type of encoded variables in the string. + */ +template +class EncodedTextAst { +public: + // Constructor + explicit EncodedTextAst( + std::string logtype, + std::vector dict_vars, + std::vector encoded_vars + ) + : m_logtype{std::move(logtype)}, + m_dict_vars{std::move(dict_vars)}, + m_encoded_vars{std::move(encoded_vars)} {} + + // Methods + auto operator==(EncodedTextAst const&) const -> bool = default; + + [[nodiscard]] auto get_logtype() const -> std::string const& { return m_logtype; } + + [[nodiscard]] auto get_dict_vars() const -> std::vector const& { + return m_dict_vars; + } + + [[nodiscard]] auto get_encoded_vars() const -> std::vector const& { + return m_encoded_vars; + } + + /** + * Decodes and un-parses the EncodedTextAst into its string form. + * @return The string corresponding to the EncodedTextAst on success. + * @return std::nullopt if decoding fails. + */ + [[nodiscard]] auto decode_and_unparse() const -> std::optional; + +private: + // Variables + std::string m_logtype; + std::vector m_dict_vars; + std::vector m_encoded_vars; +}; + +using EightByteEncodedTextAst = EncodedTextAst; +using FourByteEncodedTextAst = EncodedTextAst; +} // namespace clp::ir + +#endif // CLP_IR_ENCODEDTEXTAST_HPP diff --git a/components/core/src/clp/ir/LogEvent.hpp b/components/core/src/clp/ir/LogEvent.hpp index d32aabb41..4a3ef7567 100644 --- a/components/core/src/clp/ir/LogEvent.hpp +++ b/components/core/src/clp/ir/LogEvent.hpp @@ -2,9 +2,10 @@ #define CLP_IR_LOGEVENT_HPP #include +#include #include -#include "../Defs.h" +#include "EncodedTextAst.hpp" #include "time_types.hpp" #include "types.hpp" @@ -20,38 +21,26 @@ class LogEvent { LogEvent( epoch_time_ms_t timestamp, UtcOffset utc_offset, - std::string logtype, - std::vector dict_vars, - std::vector encoded_vars + EncodedTextAst message ) : m_timestamp{timestamp}, m_utc_offset{utc_offset}, - m_logtype{std::move(logtype)}, - m_dict_vars{std::move(dict_vars)}, - m_encoded_vars{std::move(encoded_vars)} {} + m_message{std::move(message)} {} // Methods [[nodiscard]] auto get_timestamp() const -> epoch_time_ms_t { return m_timestamp; } [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } - [[nodiscard]] auto get_logtype() const -> std::string const& { return m_logtype; } - - [[nodiscard]] auto get_dict_vars() const -> std::vector const& { - return m_dict_vars; - } - - [[nodiscard]] auto get_encoded_vars() const -> std::vector const& { - return m_encoded_vars; + [[nodiscard]] auto get_message() const -> EncodedTextAst const& { + return m_message; } private: // Variables epoch_time_ms_t m_timestamp{0}; UtcOffset m_utc_offset{0}; - std::string m_logtype; - std::vector m_dict_vars; - std::vector m_encoded_vars; + EncodedTextAst m_message; }; } // namespace clp::ir diff --git a/components/core/src/clp/ir/LogEventDeserializer.cpp b/components/core/src/clp/ir/LogEventDeserializer.cpp index b158a2712..8a1064a78 100644 --- a/components/core/src/clp/ir/LogEventDeserializer.cpp +++ b/components/core/src/clp/ir/LogEventDeserializer.cpp @@ -3,16 +3,18 @@ #include #include +#include #include #include "../ffi/ir_stream/decoding_methods.hpp" #include "../ffi/ir_stream/protocol_constants.hpp" +#include "EncodedTextAst.hpp" #include "types.hpp" namespace clp::ir { template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { ffi::ir_stream::encoded_tag_t metadata_type{0}; std::vector metadata; auto ir_error_code = ffi::ir_stream::deserialize_preamble(reader, metadata_type, metadata); @@ -40,7 +42,7 @@ auto LogEventDeserializer::create(ReaderInterface& reader return std::errc::protocol_error; } auto metadata_version = version_iter->get_ref(); - if (ffi::ir_stream::IRProtocolErrorCode_Supported + if (ffi::ir_stream::IRProtocolErrorCode::BackwardCompatible != ffi::ir_stream::validate_protocol_version(metadata_version)) { return std::errc::protocol_not_supported; @@ -68,7 +70,7 @@ auto LogEventDeserializer::create(ReaderInterface& reader template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { // Process any packets before the log event ffi::ir_stream::encoded_tag_t tag{}; while (true) { @@ -123,17 +125,21 @@ auto LogEventDeserializer::deserialize_log_event( timestamp = m_prev_msg_timestamp; } - return LogEvent{timestamp, m_utc_offset, logtype, dict_vars, encoded_vars}; + return LogEvent{ + timestamp, + m_utc_offset, + EncodedTextAst{logtype, dict_vars, encoded_vars} + }; } // Explicitly declare template specializations so that we can define the template methods in this // file template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; } // namespace clp::ir diff --git a/components/core/src/clp/ir/LogEventDeserializer.hpp b/components/core/src/clp/ir/LogEventDeserializer.hpp index 5fb72579d..a8bb4dc20 100644 --- a/components/core/src/clp/ir/LogEventDeserializer.hpp +++ b/components/core/src/clp/ir/LogEventDeserializer.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "../ReaderInterface.hpp" #include "../time_types.hpp" @@ -35,7 +35,7 @@ class LogEventDeserializer { * or uses an unsupported version */ static auto create(ReaderInterface& reader - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; // Delete copy constructor and assignment LogEventDeserializer(LogEventDeserializer const&) = delete; @@ -62,7 +62,7 @@ class LogEventDeserializer { * - std::errc::protocol_error if the IR stream is corrupted */ [[nodiscard]] auto deserialize_log_event( - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; private: // Constructors diff --git a/components/core/src/clp/ir/parsing.cpp b/components/core/src/clp/ir/parsing.cpp index 2082f0640..2a54fb6a6 100644 --- a/components/core/src/clp/ir/parsing.cpp +++ b/components/core/src/clp/ir/parsing.cpp @@ -1,5 +1,9 @@ #include "parsing.hpp" +#include +#include +#include + #include #include "../type_utils.hpp" diff --git a/components/core/src/clp/ir/parsing.hpp b/components/core/src/clp/ir/parsing.hpp index c962cf46c..26171a624 100644 --- a/components/core/src/clp/ir/parsing.hpp +++ b/components/core/src/clp/ir/parsing.hpp @@ -9,8 +9,9 @@ * the placement of the methods in this file. */ +#include +#include #include -#include namespace clp::ir { /** diff --git a/components/core/src/clp/ir/parsing.inc b/components/core/src/clp/ir/parsing.inc index 5cb8f87f0..cafd9f77c 100644 --- a/components/core/src/clp/ir/parsing.inc +++ b/components/core/src/clp/ir/parsing.inc @@ -1,6 +1,7 @@ #ifndef CLP_IR_PARSING_INC #define CLP_IR_PARSING_INC +#include #include #include diff --git a/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt b/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt index 6dc5334bf..9779d137f 100644 --- a/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt +++ b/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt @@ -4,6 +4,8 @@ set( ../dictionary_utils.hpp ../DictionaryEntry.hpp ../DictionaryReader.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -17,6 +19,8 @@ set( ../ParsedMessage.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../streaming_compression/Decompressor.hpp ../streaming_compression/passthrough/Decompressor.cpp @@ -41,7 +45,7 @@ target_compile_features(make-dictionaries-readable PRIVATE cxx_std_20) target_include_directories(make-dictionaries-readable PRIVATE "${PROJECT_SOURCE_DIR}/submodules") target_link_libraries(make-dictionaries-readable PRIVATE - Boost::filesystem Boost::iostreams Boost::program_options + Boost::filesystem Boost::program_options log_surgeon::log_surgeon spdlog::spdlog clp::string_utils diff --git a/components/core/src/clp/regex_utils/CMakeLists.txt b/components/core/src/clp/regex_utils/CMakeLists.txt new file mode 100644 index 000000000..e8fcfe556 --- /dev/null +++ b/components/core/src/clp/regex_utils/CMakeLists.txt @@ -0,0 +1,21 @@ +set( + REGEX_UTILS_HEADER_LIST + "constants.hpp" + "ErrorCode.hpp" + "regex_translation_utils.hpp" + "RegexToWildcardTranslatorConfig.hpp" +) +add_library( + regex_utils + ErrorCode.cpp + regex_translation_utils.cpp + ${REGEX_UTILS_HEADER_LIST} +) +add_library(clp::regex_utils ALIAS regex_utils) +target_include_directories(regex_utils + PUBLIC + ../ + "${PROJECT_SOURCE_DIR}/submodules" +) +target_link_libraries(regex_utils PRIVATE clp::string_utils) +target_compile_features(regex_utils PRIVATE cxx_std_20) diff --git a/components/core/src/clp/regex_utils/ErrorCode.cpp b/components/core/src/clp/regex_utils/ErrorCode.cpp new file mode 100644 index 000000000..4e24e9a8b --- /dev/null +++ b/components/core/src/clp/regex_utils/ErrorCode.cpp @@ -0,0 +1,93 @@ +#include "regex_utils/ErrorCode.hpp" + +#include +#include +#include + +namespace clp::regex_utils { +using std::error_code; + +namespace { +using std::error_category; +using std::string; +using std::string_view; + +/** + * Class for giving the error codes more detailed string descriptions. + */ +class ErrorCodeCategory : public error_category { +public: + /** + * @return The class of errors. + */ + [[nodiscard]] auto name() const noexcept -> char const* override; + + /** + * @param The error code encoded in int. + * @return The descriptive message for the error. + */ + [[nodiscard]] auto message(int ev) const -> string override; +}; + +auto ErrorCodeCategory::name() const noexcept -> char const* { + return "regex utility"; +} + +auto ErrorCodeCategory::message(int ev) const -> string { + switch (static_cast(ev)) { + case ErrorCode::Success: + return "Success."; + + case ErrorCode::IllegalState: + return "Unrecognized state."; + + case ErrorCode::UntranslatableStar: + return "Unable to express regex quantifier `*` in wildcard, which repeats a token for " + "zero or more occurences, unless it is combined with a wildcard `.`"; + + case ErrorCode::UntranslatablePlus: + return "Unable to express regex quantifier `+` in wildcard, which repeats a token for " + "one or more occurences, unless it is combined with a wildcard `.`"; + + case ErrorCode::UnsupportedQuestionMark: + return "Unable to express regex quantifier `?` in wildcard, which makes the preceding " + "token optional, unless the translator supports returning a list of possible " + "wildcard translations."; + + case ErrorCode::UnsupportedPipe: + return "Unable to express regex OR `|` in wildcard, which allows the query string to " + "match a single token out of a series of options, unless the translator " + "supports returning a list of possible wildcard translations."; + + case ErrorCode::IllegalCaret: + return "Failed to translate due to start anchor `^` in the middle of the string."; + + case ErrorCode::IllegalDollarSign: + return "Failed to translate due to end anchor `$` in the middle of the string."; + + case ErrorCode::IllegalEscapeSequence: + return "Currently only supports escape sequences that are used to suppress special " + "meanings of regex metacharacters. Alphanumeric characters are disallowed."; + + case ErrorCode::UnmatchedParenthesis: + return "Unmatched opening `(` or closing `)`."; + + case ErrorCode::IncompleteCharsetStructure: + return "Unmatched closing `]` at the end of the string."; + + case ErrorCode::UnsupportedCharsetPattern: + return "Currently only supports character set that can be reduced to a single " + "character."; + + default: + return "(unrecognized error)"; + } +} + +ErrorCodeCategory const cErrorCodeCategoryInstance; +} // namespace + +auto make_error_code(ErrorCode e) -> error_code { + return {static_cast(e), cErrorCodeCategoryInstance}; +} +} // namespace clp::regex_utils diff --git a/components/core/src/clp/regex_utils/ErrorCode.hpp b/components/core/src/clp/regex_utils/ErrorCode.hpp new file mode 100644 index 000000000..9b4fbf8f2 --- /dev/null +++ b/components/core/src/clp/regex_utils/ErrorCode.hpp @@ -0,0 +1,42 @@ +#ifndef CLP_REGEX_UTILS_ERRORCODE_HPP +#define CLP_REGEX_UTILS_ERRORCODE_HPP + +#include +#include +#include + +namespace clp::regex_utils { +/** + * Enum class for propagating and handling various regex utility errors. + * More detailed descriptions can be found in ErrorCode.cpp. + */ +enum class ErrorCode : uint8_t { + Success = 0, + IllegalState, + UntranslatableStar, + UntranslatablePlus, + UnsupportedQuestionMark, + UnsupportedPipe, + IllegalCaret, + IllegalDollarSign, + IllegalEscapeSequence, + UnmatchedParenthesis, + IncompleteCharsetStructure, + UnsupportedCharsetPattern, +}; + +/** + * Wrapper function to turn a regular enum class into an std::error_code. + * + * @param An error code enum. + * @return The corresponding std::error_code type variable. + */ +[[nodiscard]] auto make_error_code(ErrorCode ec) -> std::error_code; +} // namespace clp::regex_utils + +namespace std { +template <> +struct is_error_code_enum : true_type {}; +} // namespace std + +#endif // CLP_REGEX_UTILS_ERRORCODE_HPP diff --git a/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp b/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp new file mode 100644 index 000000000..e53963c2e --- /dev/null +++ b/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp @@ -0,0 +1,46 @@ +#ifndef CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP +#define CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP + +namespace clp::regex_utils { +/** + * Allows users to customize and fine tune how to translate a regex string to wildcard. + * + * This class won't affect the core logic and state trasition mechanics of the regex to wildcard + * translator, but it can make the translator more versatile. For detailed descriptions of how each + * option should be used, see the getter function docstrings. + */ +class RegexToWildcardTranslatorConfig { +public: + RegexToWildcardTranslatorConfig( + bool case_insensitive_wildcard, + bool add_prefix_suffix_wildcards + ) + : m_case_insensitive_wildcard{case_insensitive_wildcard}, + m_add_prefix_suffix_wildcards{add_prefix_suffix_wildcards} {}; + + /** + * @return True if the final translated wildcard string will be fed into a case-insensitive + * wildcard analyzer. In such cases, we can safely translate charset patterns such as [aA] [Bb] + * into singular lowercase characters a, b. + */ + [[nodiscard]] auto case_insensitive_wildcard() const -> bool { + return m_case_insensitive_wildcard; + } + + /** + * @return True if in the absense of starting or ending anchors in the regex string, we append + * prefix or suffix zero or more characters wildcards. In other words, this config is true if + * the search is a substring search, and false if the search is an exact search. + */ + [[nodiscard]] auto add_prefix_suffix_wildcards() const -> bool { + return m_add_prefix_suffix_wildcards; + } + +private: + // Variables + bool m_case_insensitive_wildcard; + bool m_add_prefix_suffix_wildcards; +}; +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP diff --git a/components/core/src/clp/regex_utils/constants.hpp b/components/core/src/clp/regex_utils/constants.hpp new file mode 100644 index 000000000..ff2eb5b10 --- /dev/null +++ b/components/core/src/clp/regex_utils/constants.hpp @@ -0,0 +1,52 @@ +#ifndef CLP_REGEX_UTILS_CONSTANTS_HPP +#define CLP_REGEX_UTILS_CONSTANTS_HPP + +#include +#include +#include + +namespace clp::regex_utils { +constexpr size_t cCharBitarraySize = 128; + +/** + * Creates an ASCII character lookup table at compile time. + * + * @param char_str A string that contains the characters to look up. + * @return The lookup table as bit array. + */ +[[nodiscard]] constexpr auto create_char_bit_array(std::string_view char_str +) -> std::array { + std::array bit_array{}; + bit_array.fill(false); + for (auto const ch : char_str) { + bit_array.at(ch) = true; + } + return bit_array; +} + +// Wildcard meta characters +constexpr char cZeroOrMoreCharsWildcard{'*'}; +constexpr char cSingleCharWildcard{'?'}; + +// Regex meta characters +constexpr char cRegexZeroOrMore{'*'}; +constexpr char cRegexOneOrMore{'+'}; +constexpr char cRegexZeroOrOne{'?'}; +constexpr char cRegexStartAnchor{'^'}; +constexpr char cRegexEndAnchor{'$'}; +constexpr char cEscapeChar{'\\'}; +constexpr char cCharsetNegate{'^'}; + +// Character bitmaps +// The set of regex metacharacters that can be preceded with an escape backslash to be treated as a +// literal. +constexpr auto cRegexEscapeSeqMetaCharsLut = create_char_bit_array("*+?|^$.{}[]()<>-_/=!\\"); +// The set of wildcard metacharacters that must remain escaped in the translated string to be +// treated as a literal. +constexpr auto cWildcardMetaCharsLut = create_char_bit_array("?*\\"); +// The set of metacharacters that can be preceded with an escape backslash in the regex character +// set to be treated as a literal. +constexpr auto cRegexCharsetEscapeSeqMetaCharsLut = create_char_bit_array("^-]\\"); +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_CONSTANTS_HPP diff --git a/components/core/src/clp/regex_utils/regex_translation_utils.cpp b/components/core/src/clp/regex_utils/regex_translation_utils.cpp new file mode 100644 index 000000000..f1a987006 --- /dev/null +++ b/components/core/src/clp/regex_utils/regex_translation_utils.cpp @@ -0,0 +1,425 @@ +#include "regex_utils/regex_translation_utils.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "regex_utils/constants.hpp" +#include "regex_utils/ErrorCode.hpp" +#include "regex_utils/RegexToWildcardTranslatorConfig.hpp" + +namespace clp::regex_utils { +using clp::string_utils::is_alphabet; +using std::error_code; +using std::optional; +using std::string; +using std::string_view; + +namespace { +/** + * Class for storing regex translation analysis states, capture group, quantifier information, etc. + */ +class TranslatorState { +public: + /** + * States for which we apply specific rules to translate encountered regex patterns. + * + * This list may be expanded as the translator supports translating more regex patterns. + *
    + *
  • Normal: The initial state, where characters have no special meanings and are treated + * literally.
  • + *
  • Dot: Encountered a period `.`. Expecting wildcard expression.
  • + *
  • Escaped: Encountered a backslash `\`. Expecting an escape sequence.
  • + *
  • Charset: Encountered an opening square bracket `[`. Expecting a character set.
  • + *
  • CharsetEscaped: Encountered an escape backslash in the character set.
  • + *
  • End: Encountered a dollar sign `$`, meaning the regex string has reached the end + * anchor.
  • + *
+ */ + enum class RegexPatternState : uint8_t { + Normal = 0, + Dot, + Escaped, + Charset, + CharsetEscaped, + End, + }; + + // Constructor + TranslatorState() = default; + + // Getters + [[nodiscard]] auto get_state() const -> RegexPatternState { return m_state; } + + [[nodiscard]] auto get_charset_begin_it() const -> optional { + return m_charset_begin_it; + } + + // Setters + auto set_next_state(RegexPatternState const& state) -> void { m_state = state; } + + auto set_charset_begin_it(string_view::const_iterator charset_begin_it) -> void { + m_charset_begin_it = charset_begin_it; + } + + auto invalidate_charset_begin_it() -> void { m_charset_begin_it.reset(); } + +private: + // Members + RegexPatternState m_state{RegexPatternState::Normal}; + optional m_charset_begin_it; +}; + +/** + * Functions that handle current-state-specific tasks before transitioning to the next state. + * + * @param[in, out] state The object that stores translator's internal information. The primary + * state member variable is always updated if a transition occures. Even if there's no state + * transition, other analysis info may be updated. + * @param[in, out] it The iterator that represents the current regex string scan position. May be + * updated to advance or backtrack the scan position. + * @param[out] wildcard_str The translated wildcard string. May or may not be updated. + * @param[in] config The translator config predefined by the user. + * @return clp::regex_utils::ErrorCode + */ +using StateTransitionFuncSig + = auto(TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config) -> error_code; + +/** + * Treats each character literally and directly append it to the wildcard string, unless it is a + * meta-character. + * + * Each meta-character either triggers a state transition, or makes the regex string untranslatable. + */ +[[nodiscard]] StateTransitionFuncSig normal_state_transition; + +/** + * Attempts to translate regex wildcard patterns that start with `.` character. + * + * Performs the following translation if possible: + *
    + *
  • `.*` gets translated into `*`
  • + *
  • `.+` gets translated into `?*`
  • + *
  • `.` gets translated into `?`
  • + *
+ */ +[[nodiscard]] StateTransitionFuncSig dot_state_transition; + +/** + * Appends an escaped regex metacharacter as a literal character to the wildcard string by + * discarding its preceding backslash. + * + * The preceding backslash must be kept for characters that also have special meanings in the + * wildcard syntax, e.g. `abc.\*xyz` should be translated into `abc?\*xyz` instead of `abc?*xyz`. + */ +[[nodiscard]] StateTransitionFuncSig escaped_state_transition; + +/** + * Reduces a regex character set into a single character so that the regex string is still + * translatable into a wildcard string. + * + * In most cases, only a trival character set containing a single character is reducable. However, + * if the output wildcard query will be analyzed in case-insensitive mode, character set patterns + * such as [aA] [Bb] are also reducable. Does not support empty charsets. + * On error, returns IncompleteCharsetStructure, UnsupportedCharsetPattern, or IllegalState. + */ +[[nodiscard]] StateTransitionFuncSig charset_state_transition; + +/** + * A transient state used to defer handling of escape sequences in a charset pattern. + * + * Allows the charset state to accurately capture the appearance of a closing bracket `]`. + */ +[[nodiscard]] StateTransitionFuncSig charset_escaped_state_transition; + +/** + * Disallows the appearances of other characters after encountering an end anchor in the string. + */ +[[nodiscard]] StateTransitionFuncSig end_state_transition; + +/** + * States other than the Normal state may require special handling after the whole regex string has + * been scanned and processed. + */ +[[nodiscard]] StateTransitionFuncSig final_state_cleanup; + +/** + * Appends a single character as a literal to the wildcard string. + * + * If the literal is a metacharacter in the wildcard syntax, prepend the literal with an escape + * backslash. + * @param ch The literal to be appended. + * @param wildcard_str The wildcard string to be updated. + */ +auto append_char_to_wildcard(char ch, string& wildcard_str) -> void; + +/** + * @param ch0 + * @param ch1 + * @return Whether the given chars are the same, but with opposing letter cases, e.g. 'A' vs. 'a'. + */ +[[nodiscard]] auto is_same_char_opposite_case(char ch0, char ch1) -> bool; + +auto normal_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const ch{*it}; + switch (ch) { + case '.': + state.set_next_state(TranslatorState::RegexPatternState::Dot); + break; + case cEscapeChar: + state.set_next_state(TranslatorState::RegexPatternState::Escaped); + break; + case '[': + state.set_charset_begin_it(it + 1); + state.set_next_state(TranslatorState::RegexPatternState::Charset); + break; + case cRegexEndAnchor: + state.set_next_state(TranslatorState::RegexPatternState::End); + break; + case cRegexZeroOrMore: + return ErrorCode::UntranslatableStar; + case cRegexOneOrMore: + return ErrorCode::UntranslatablePlus; + case cRegexZeroOrOne: + return ErrorCode::UnsupportedQuestionMark; + case '|': + return ErrorCode::UnsupportedPipe; + case cRegexStartAnchor: + return ErrorCode::IllegalCaret; + case ')': + return ErrorCode::UnmatchedParenthesis; + default: + wildcard_str += ch; + break; + } + return ErrorCode::Success; +} + +auto dot_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + switch (*it) { + case cZeroOrMoreCharsWildcard: + wildcard_str += cZeroOrMoreCharsWildcard; + break; + case cRegexOneOrMore: + wildcard_str = wildcard_str + cSingleCharWildcard + cZeroOrMoreCharsWildcard; + break; + default: + wildcard_str += cSingleCharWildcard; + // Backtrack the scan by one position to handle the current char in the next iteration. + --it; + break; + } + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto escaped_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const ch{*it}; + if (false == cRegexEscapeSeqMetaCharsLut.at(ch)) { + return ErrorCode::IllegalEscapeSequence; + } + append_char_to_wildcard(ch, wildcard_str); + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto charset_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const charset_begin_it_opt{state.get_charset_begin_it()}; + if (false == charset_begin_it_opt.has_value()) { + return ErrorCode::IllegalState; + } + string_view::const_iterator const charset_begin_it = charset_begin_it_opt.value(); + + auto const ch{*it}; + if (cEscapeChar == ch) { + state.set_next_state(TranslatorState::RegexPatternState::CharsetEscaped); + return ErrorCode::Success; + } + if (']' != ch) { + return ErrorCode::Success; + } + + auto const charset_len{it - charset_begin_it}; + if (0 == charset_len || charset_len > 2) { + return ErrorCode::UnsupportedCharsetPattern; + } + + auto const ch0{*charset_begin_it}; + auto const ch1{*(charset_begin_it + 1)}; + char parsed_char{}; + + if (1 == charset_len) { + if (cCharsetNegate == ch0 || cEscapeChar == ch0) { + return ErrorCode::UnsupportedCharsetPattern; + } + parsed_char = ch0; + } else { // 2 == charset_len + if (cEscapeChar == ch0 && cRegexCharsetEscapeSeqMetaCharsLut.at(ch1)) { + parsed_char = ch1; + } else if (config.case_insensitive_wildcard() && is_same_char_opposite_case(ch0, ch1)) { + parsed_char = ch0 > ch1 ? ch0 : ch1; // choose the lower case character + } else { + return ErrorCode::UnsupportedCharsetPattern; + } + } + + append_char_to_wildcard(parsed_char, wildcard_str); + state.invalidate_charset_begin_it(); + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto charset_escaped_state_transition( + TranslatorState& state, + [[maybe_unused]] string_view::const_iterator& it, + [[maybe_unused]] string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + state.set_next_state(TranslatorState::RegexPatternState::Charset); + return ErrorCode::Success; +} + +auto end_state_transition( + [[maybe_unused]] TranslatorState& state, + string_view::const_iterator& it, + [[maybe_unused]] string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + if (cRegexEndAnchor != *it) { + return ErrorCode::IllegalDollarSign; + } + return ErrorCode::Success; +} + +auto final_state_cleanup( + TranslatorState& state, + [[maybe_unused]] string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config +) -> error_code { + switch (state.get_state()) { + case TranslatorState::RegexPatternState::Dot: + // The last character is a single `.`, without the possibility of becoming a + // multichar wildcard + wildcard_str += cSingleCharWildcard; + break; + case TranslatorState::RegexPatternState::Charset: + case TranslatorState::RegexPatternState::CharsetEscaped: + return ErrorCode::IncompleteCharsetStructure; + default: + break; + } + + if (TranslatorState::RegexPatternState::End != state.get_state() + && config.add_prefix_suffix_wildcards()) + { + wildcard_str += cZeroOrMoreCharsWildcard; + } + return ErrorCode::Success; +} + +auto append_char_to_wildcard(char ch, string& wildcard_str) -> void { + if (cWildcardMetaCharsLut.at(ch)) { + wildcard_str += cEscapeChar; + } + wildcard_str += ch; +} + +auto is_same_char_opposite_case(char ch0, char ch1) -> bool { + int const upper_lower_case_ascii_offset{'a' - 'A'}; + return (is_alphabet(ch0) && is_alphabet(ch1) + && (((ch0 - ch1) == upper_lower_case_ascii_offset) + || ((ch1 - ch0) == upper_lower_case_ascii_offset))); +} +} // namespace + +auto regex_to_wildcard(string_view regex_str) -> OUTCOME_V2_NAMESPACE::std_result { + return regex_to_wildcard( + regex_str, + {/*case_insensitive_wildcard=*/false, /*add_prefix_suffix_wildcards=*/false} + ); +} + +auto regex_to_wildcard(string_view regex_str, RegexToWildcardTranslatorConfig const& config) + -> OUTCOME_V2_NAMESPACE::std_result { + if (regex_str.empty()) { + return string{}; + } + + string_view::const_iterator it{regex_str.cbegin()}; + string wildcard_str; + TranslatorState state; + + // If there is no starting anchor character, append multichar wildcard prefix + if (cRegexStartAnchor == *it) { + ++it; + } else if (config.add_prefix_suffix_wildcards()) { + wildcard_str += cZeroOrMoreCharsWildcard; + } + + error_code ec{}; + while (it != regex_str.cend()) { + switch (state.get_state()) { + case TranslatorState::RegexPatternState::Normal: + ec = normal_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Dot: + ec = dot_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Escaped: + ec = escaped_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Charset: + ec = charset_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::CharsetEscaped: + ec = charset_escaped_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::End: + ec = end_state_transition(state, it, wildcard_str, config); + break; + default: + ec = ErrorCode::IllegalState; + break; + } + if (ec) { + return ec; + } + ++it; + } + + ec = final_state_cleanup(state, it, wildcard_str, config); + if (ec) { + return ec; + } + return wildcard_str; +} +} // namespace clp::regex_utils diff --git a/components/core/src/clp/regex_utils/regex_translation_utils.hpp b/components/core/src/clp/regex_utils/regex_translation_utils.hpp new file mode 100644 index 000000000..8ca703403 --- /dev/null +++ b/components/core/src/clp/regex_utils/regex_translation_utils.hpp @@ -0,0 +1,36 @@ +#ifndef CLP_REGEX_UTILS_REGEX_UTILS_HPP +#define CLP_REGEX_UTILS_REGEX_UTILS_HPP + +#include +#include + +#include + +#include "regex_utils/RegexToWildcardTranslatorConfig.hpp" + +namespace clp::regex_utils { + +/** + * Translate a given regex string to wildcard with the default configuration that has all the + * options set to false. + * + * @param regex_str The regex string to be translated. + * @return The translated wildcard string. + */ +[[nodiscard]] auto regex_to_wildcard(std::string_view regex_str +) -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * Translate a given regex string to wildcard with a custom configuration. + * + * @param regex_str The regex string to be translated. + * @return The translated wildcard string. + */ +[[nodiscard]] auto regex_to_wildcard( + std::string_view regex_str, + RegexToWildcardTranslatorConfig const& config +) -> OUTCOME_V2_NAMESPACE::std_result; + +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_REGEX_UTILS_HPP diff --git a/components/core/src/clp/spdlog_with_specializations.hpp b/components/core/src/clp/spdlog_with_specializations.hpp index 24771f44e..e80993592 100644 --- a/components/core/src/clp/spdlog_with_specializations.hpp +++ b/components/core/src/clp/spdlog_with_specializations.hpp @@ -16,7 +16,7 @@ struct fmt::formatter { } template - auto format(clp::ErrorCode const& error_code, FormatContext& ctx) { + auto format(clp::ErrorCode const& error_code, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", static_cast(error_code)); } }; @@ -30,7 +30,8 @@ struct fmt::formatter> template auto - format(clp::ffi::search::ExactVariableToken const& v, FormatContext& ctx) { + format(clp::ffi::search::ExactVariableToken const& v, + FormatContext& ctx) const { return fmt::format_to( ctx.out(), "ExactVariableToken(\"{}\") as {}", @@ -48,7 +49,8 @@ struct fmt::formatter> { } template - auto format(clp::ffi::search::WildcardToken const& v, FormatContext& ctx) { + auto + format(clp::ffi::search::WildcardToken const& v, FormatContext& ctx) const { return fmt::format_to( ctx.out(), "WildcardToken(\"{}\") as {}TokenType({}){}", diff --git a/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp b/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp index 7b40022a9..9cf578eb8 100644 --- a/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp +++ b/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp @@ -26,6 +26,7 @@ ArchiveMetadata::ArchiveMetadata(FileReader& file_reader) { file_reader.read_numeric_value(m_archive_format_version, false); file_reader.read_numeric_value(m_creator_id_len, false); file_reader.read_string(m_creator_id_len, m_creator_id, false); + file_reader.read_numeric_value(m_creation_idx, false); file_reader.read_numeric_value(m_uncompressed_size, false); file_reader.read_numeric_value(m_compressed_size, false); file_reader.read_numeric_value(m_begin_timestamp, false); diff --git a/components/core/src/clp/streaming_archive/reader/Archive.cpp b/components/core/src/clp/streaming_archive/reader/Archive.cpp index 05e42cac3..141e5fce5 100644 --- a/components/core/src/clp/streaming_archive/reader/Archive.cpp +++ b/components/core/src/clp/streaming_archive/reader/Archive.cpp @@ -37,11 +37,9 @@ void Archive::open(string const& path) { string metadata_file_path = path + '/' + cMetadataFileName; archive_format_version_t format_version{}; try { - FileReader file_reader; - file_reader.open(metadata_file_path); + FileReader file_reader{metadata_file_path}; ArchiveMetadata const metadata{file_reader}; format_version = metadata.get_archive_format_version(); - file_reader.close(); } catch (TraceableException& traceable_exception) { auto error_code = traceable_exception.get_error_code(); if (ErrorCode_errno == error_code) { diff --git a/components/core/src/clp/streaming_archive/reader/Segment.cpp b/components/core/src/clp/streaming_archive/reader/Segment.cpp index aa43e1d1f..7732dc5f8 100644 --- a/components/core/src/clp/streaming_archive/reader/Segment.cpp +++ b/components/core/src/clp/streaming_archive/reader/Segment.cpp @@ -3,12 +3,16 @@ #include #include +#include #include #include +#include +#include "../../ErrorCode.hpp" #include "../../FileReader.hpp" #include "../../spdlog_with_specializations.hpp" +#include "../../TraceableException.hpp" using std::make_unique; using std::string; @@ -33,47 +37,37 @@ ErrorCode Segment::try_open(string const& segment_dir_path, segment_id_t segment return ErrorCode_Success; } - // Get the size of the compressed segment file - boost::system::error_code boost_error_code; - size_t segment_file_size = boost::filesystem::file_size(segment_path, boost_error_code); - if (boost_error_code) { - SPDLOG_ERROR( - "streaming_archive::reader::Segment: Unable to obtain file size for segment: " - "{}", - segment_path.c_str() - ); - SPDLOG_ERROR("streaming_archive::reader::Segment: {}", boost_error_code.message().c_str()); - return ErrorCode_Failure; - } - - // Sanity check: previously used memory mapped file should be closed before opening a new - // one - if (m_memory_mapped_segment_file.is_open()) { + // Sanity check: previously used memory mapped file should be closed before opening a new one + if (m_memory_mapped_segment_file.has_value()) { SPDLOG_WARN( "streaming_archive::reader::Segment: Previous segment should be closed before " "opening new one: {}", segment_path.c_str() ); - m_memory_mapped_segment_file.close(); + m_memory_mapped_segment_file.reset(); } - // Create read only memory mapped file - boost::iostreams::mapped_file_params memory_map_params; - memory_map_params.path = segment_path; - memory_map_params.flags = boost::iostreams::mapped_file::readonly; - memory_map_params.length = segment_file_size; - // Try to map it to the same memory location as the previous memory mapped file - memory_map_params.hint = m_memory_mapped_segment_file.data(); - m_memory_mapped_segment_file.open(memory_map_params); - if (!m_memory_mapped_segment_file.is_open()) { + + // Create read-only memory mapped file + try { + m_memory_mapped_segment_file.emplace(segment_path); + } catch (TraceableException const& ex) { + auto const error_code{ex.get_error_code()}; + auto const formatted_error{ + ErrorCode_errno == error_code + ? fmt::format("errno={}", errno) + : fmt::format("error_code={}, message={}", error_code, ex.what()) + }; SPDLOG_ERROR( "streaming_archive::reader:Segment: Unable to memory map the compressed " - "segment with path: {}", - segment_path.c_str() + "segment with path: {}. Error: {}", + segment_path.c_str(), + formatted_error ); return ErrorCode_Failure; } - m_decompressor.open(m_memory_mapped_segment_file.data(), segment_file_size); + auto const view{m_memory_mapped_segment_file.value().get_view()}; + m_decompressor.open(view.data(), view.size()); m_segment_path = segment_path; return ErrorCode_Success; @@ -82,7 +76,7 @@ ErrorCode Segment::try_open(string const& segment_dir_path, segment_id_t segment void Segment::close() { if (!m_segment_path.empty()) { m_decompressor.close(); - m_memory_mapped_segment_file.close(); + m_memory_mapped_segment_file.reset(); m_segment_path.clear(); } } diff --git a/components/core/src/clp/streaming_archive/reader/Segment.hpp b/components/core/src/clp/streaming_archive/reader/Segment.hpp index 9ed40ea60..cdcd51b4d 100644 --- a/components/core/src/clp/streaming_archive/reader/Segment.hpp +++ b/components/core/src/clp/streaming_archive/reader/Segment.hpp @@ -2,12 +2,12 @@ #define CLP_STREAMING_ARCHIVE_READER_SEGMENT_HPP #include +#include #include -#include - #include "../../Defs.h" #include "../../ErrorCode.hpp" +#include "../../ReadOnlyMemoryMappedFile.hpp" #include "../../streaming_compression/passthrough/Decompressor.hpp" #include "../../streaming_compression/zstd/Decompressor.hpp" #include "../Constants.hpp" @@ -53,7 +53,7 @@ class Segment { private: std::string m_segment_path; - boost::iostreams::mapped_file_source m_memory_mapped_segment_file; + std::optional m_memory_mapped_segment_file; #if USE_PASSTHROUGH_COMPRESSION streaming_compression::passthrough::Decompressor m_decompressor; diff --git a/components/core/src/clp/streaming_compression/Compressor.hpp b/components/core/src/clp/streaming_compression/Compressor.hpp index 165696091..ac55bd270 100644 --- a/components/core/src/clp/streaming_compression/Compressor.hpp +++ b/components/core/src/clp/streaming_compression/Compressor.hpp @@ -1,14 +1,19 @@ #ifndef CLP_STREAMING_COMPRESSION_COMPRESSOR_HPP #define CLP_STREAMING_COMPRESSION_COMPRESSOR_HPP -#include -#include +#include +#include + +#include "../ErrorCode.hpp" +#include "../FileWriter.hpp" #include "../TraceableException.hpp" #include "../WriterInterface.hpp" -#include "Constants.hpp" namespace clp::streaming_compression { +/** + * Abstract compressor interface. + */ class Compressor : public WriterInterface { public: // Types @@ -16,23 +21,27 @@ class Compressor : public WriterInterface { public: // Constructors OperationFailed(ErrorCode error_code, char const* const filename, int line_number) - : TraceableException(error_code, filename, line_number) {} + : TraceableException{error_code, filename, line_number} {} // Methods - char const* what() const noexcept override { + [[nodiscard]] auto what() const noexcept -> char const* override { return "streaming_compression::Compressor operation failed"; } }; // Constructor - explicit Compressor(CompressorType type) : m_type(type) {} + Compressor() = default; // Destructor virtual ~Compressor() = default; - // Explicitly disable copy and move constructor/assignment + // Delete copy constructor and assignment operator Compressor(Compressor const&) = delete; - Compressor& operator=(Compressor const&) = delete; + auto operator=(Compressor const&) -> Compressor& = delete; + + // Default move constructor and assignment operator + Compressor(Compressor&&) noexcept = default; + auto operator=(Compressor&&) noexcept -> Compressor& = default; // Methods implementing the WriterInterface /** @@ -40,24 +49,30 @@ class Compressor : public WriterInterface { * @param pos * @return ErrorCode_Unsupported */ - ErrorCode try_seek_from_begin(size_t pos) override { return ErrorCode_Unsupported; } + [[nodiscard]] auto try_seek_from_begin([[maybe_unused]] size_t pos) -> ErrorCode override { + return ErrorCode_Unsupported; + } /** * Unsupported operation * @param pos * @return ErrorCode_Unsupported */ - ErrorCode try_seek_from_current(off_t offset) override { return ErrorCode_Unsupported; } + [[nodiscard]] auto try_seek_from_current([[maybe_unused]] off_t offset) -> ErrorCode override { + return ErrorCode_Unsupported; + } // Methods /** - * Closes the compression stream + * Closes the compressor */ - virtual void close() = 0; + virtual auto close() -> void = 0; -protected: - // Variables - CompressorType m_type; + /** + * Initializes the compression stream + * @param file_writer + */ + virtual auto open(FileWriter& file_writer) -> void = 0; }; } // namespace clp::streaming_compression diff --git a/components/core/src/clp/streaming_compression/passthrough/Compressor.cpp b/components/core/src/clp/streaming_compression/passthrough/Compressor.cpp index 750ab48c1..d4ab89dbe 100644 --- a/components/core/src/clp/streaming_compression/passthrough/Compressor.cpp +++ b/components/core/src/clp/streaming_compression/passthrough/Compressor.cpp @@ -1,9 +1,12 @@ #include "Compressor.hpp" -#include "../../Defs.h" +#include + +#include "../../ErrorCode.hpp" +#include "../../TraceableException.hpp" namespace clp::streaming_compression::passthrough { -void Compressor::write(char const* data, size_t const data_length) { +auto Compressor::write(char const* data, size_t const data_length) -> void { if (nullptr == m_compressed_stream_file_writer) { throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); } @@ -19,7 +22,7 @@ void Compressor::write(char const* data, size_t const data_length) { m_compressed_stream_file_writer->write(data, data_length); } -void Compressor::flush() { +auto Compressor::flush() -> void { if (nullptr == m_compressed_stream_file_writer) { throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); } @@ -27,7 +30,7 @@ void Compressor::flush() { m_compressed_stream_file_writer->flush(); } -ErrorCode Compressor::try_get_pos(size_t& pos) const { +auto Compressor::try_get_pos(size_t& pos) const -> ErrorCode { if (nullptr == m_compressed_stream_file_writer) { return ErrorCode_NotInit; } @@ -35,11 +38,11 @@ ErrorCode Compressor::try_get_pos(size_t& pos) const { return m_compressed_stream_file_writer->try_get_pos(pos); } -void Compressor::close() { +auto Compressor::close() -> void { m_compressed_stream_file_writer = nullptr; } -void Compressor::open(FileWriter& file_writer) { +auto Compressor::open(FileWriter& file_writer) -> void { m_compressed_stream_file_writer = &file_writer; } } // namespace clp::streaming_compression::passthrough diff --git a/components/core/src/clp/streaming_compression/passthrough/Compressor.hpp b/components/core/src/clp/streaming_compression/passthrough/Compressor.hpp index b3735bd1e..f7ccd5004 100644 --- a/components/core/src/clp/streaming_compression/passthrough/Compressor.hpp +++ b/components/core/src/clp/streaming_compression/passthrough/Compressor.hpp @@ -1,6 +1,9 @@ #ifndef CLP_STREAMING_COMPRESSION_PASSTHROUGH_COMPRESSOR_HPP #define CLP_STREAMING_COMPRESSION_PASSTHROUGH_COMPRESSOR_HPP +#include + +#include "../../ErrorCode.hpp" #include "../../FileWriter.hpp" #include "../../TraceableException.hpp" #include "../Compressor.hpp" @@ -16,22 +19,27 @@ class Compressor : public ::clp::streaming_compression::Compressor { public: // Constructors OperationFailed(ErrorCode error_code, char const* const filename, int line_number) - : TraceableException(error_code, filename, line_number) {} + : TraceableException{error_code, filename, line_number} {} // Methods - char const* what() const noexcept override { + [[nodiscard]] auto what() const noexcept -> char const* override { return "streaming_compression::passthrough::Compressor operation failed"; } }; - // Constructors - Compressor() - : ::clp::streaming_compression::Compressor(CompressorType::Passthrough), - m_compressed_stream_file_writer(nullptr) {} + // Constructor + Compressor() = default; + + // Destructor + ~Compressor() override = default; - // Explicitly disable copy and move constructor/assignment + // Delete copy constructor and assignment operator Compressor(Compressor const&) = delete; - Compressor& operator=(Compressor const&) = delete; + auto operator=(Compressor const&) -> Compressor& = delete; + + // Default move constructor and assignment operator + Compressor(Compressor&&) noexcept = default; + auto operator=(Compressor&&) noexcept -> Compressor& = default; // Methods implementing the WriterInterface /** @@ -39,35 +47,36 @@ class Compressor : public ::clp::streaming_compression::Compressor { * @param data * @param data_length */ - void write(char const* data, size_t data_length) override; + auto write(char const* data, size_t data_length) -> void override; + /** * Flushes any buffered data */ - void flush() override; + auto flush() -> void override; + /** * Tries to get the current position of the write head * @param pos Position of the write head * @return ErrorCode_NotInit if the compressor is not open * @return Same as FileWriter::try_get_pos */ - ErrorCode try_get_pos(size_t& pos) const override; + [[nodiscard]] auto try_get_pos(size_t& pos) const -> ErrorCode override; // Methods implementing the Compressor interface /** * Closes the compressor */ - void close() override; + auto close() -> void override; - // Methods /** - * Initializes the compressor + * Initializes the compression stream * @param file_writer */ - void open(FileWriter& file_writer); + auto open(FileWriter& file_writer) -> void override; private: // Variables - FileWriter* m_compressed_stream_file_writer; + FileWriter* m_compressed_stream_file_writer{nullptr}; }; } // namespace clp::streaming_compression::passthrough diff --git a/components/core/src/clp/streaming_compression/zstd/Compressor.cpp b/components/core/src/clp/streaming_compression/zstd/Compressor.cpp index ebbf9b574..948ec1967 100644 --- a/components/core/src/clp/streaming_compression/zstd/Compressor.cpp +++ b/components/core/src/clp/streaming_compression/zstd/Compressor.cpp @@ -1,14 +1,21 @@ #include "Compressor.hpp" -#include "../../Defs.h" -#include "../../spdlog_with_specializations.hpp" +#include + +#include +#include + +#include "../../ErrorCode.hpp" +#include "../../FileWriter.hpp" +#include "../../TraceableException.hpp" namespace clp::streaming_compression::zstd { Compressor::Compressor() - : ::clp::streaming_compression::Compressor(CompressorType::ZSTD), - m_compression_stream_contains_data(false), - m_compressed_stream_file_writer(nullptr) { - m_compression_stream = ZSTD_createCStream(); + : m_compressed_stream_block{ + .dst = m_compressed_stream_block_buffer.data(), + .size = m_compressed_stream_block_buffer.size(), + .pos = 0 + } { if (nullptr == m_compression_stream) { SPDLOG_ERROR("streaming_compression::zstd::Compressor: ZSTD_createCStream() error"); throw OperationFailed(ErrorCode_Failure, __FILENAME__, __LINE__); @@ -19,20 +26,14 @@ Compressor::~Compressor() { ZSTD_freeCStream(m_compression_stream); } -void Compressor::open(FileWriter& file_writer, int const compression_level) { +auto Compressor::open(FileWriter& file_writer, int compression_level) -> void { if (nullptr != m_compressed_stream_file_writer) { throw OperationFailed(ErrorCode_NotReady, __FILENAME__, __LINE__); } - // Setup compressed stream parameters - size_t compressed_stream_block_size = ZSTD_CStreamOutSize(); - m_compressed_stream_block_buffer = std::make_unique(compressed_stream_block_size); - m_compressed_stream_block.dst = m_compressed_stream_block_buffer.get(); - m_compressed_stream_block.size = compressed_stream_block_size; - // Setup compression stream - auto init_result = ZSTD_initCStream(m_compression_stream, compression_level); - if (ZSTD_isError(init_result)) { + auto const init_result{ZSTD_initCStream(m_compression_stream, compression_level)}; + if (0 != ZSTD_isError(init_result)) { SPDLOG_ERROR( "streaming_compression::zstd::Compressor: ZSTD_initCStream() error: {}", ZSTD_getErrorName(init_result) @@ -45,7 +46,7 @@ void Compressor::open(FileWriter& file_writer, int const compression_level) { m_uncompressed_stream_pos = 0; } -void Compressor::close() { +auto Compressor::close() -> void { if (nullptr == m_compressed_stream_file_writer) { throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); } @@ -54,7 +55,7 @@ void Compressor::close() { m_compressed_stream_file_writer = nullptr; } -void Compressor::write(char const* data, size_t data_length) { +auto Compressor::write(char const* data, size_t data_length) -> void { if (nullptr == m_compressed_stream_file_writer) { throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); } @@ -70,23 +71,23 @@ void Compressor::write(char const* data, size_t data_length) { ZSTD_inBuffer uncompressed_stream_block = {data, data_length, 0}; while (uncompressed_stream_block.pos < uncompressed_stream_block.size) { m_compressed_stream_block.pos = 0; - auto error = ZSTD_compressStream( + auto const compress_result{ZSTD_compressStream( m_compression_stream, &m_compressed_stream_block, &uncompressed_stream_block - ); - if (ZSTD_isError(error)) { + )}; + if (0 != ZSTD_isError(compress_result)) { SPDLOG_ERROR( "streaming_compression::zstd::Compressor: ZSTD_compressStream() error: {}", - ZSTD_getErrorName(error) + ZSTD_getErrorName(compress_result) ); throw OperationFailed(ErrorCode_Failure, __FILENAME__, __LINE__); } - if (m_compressed_stream_block.pos) { + if (m_compressed_stream_block.pos > 0) { // Write to disk only if there is data in the compressed stream // block buffer m_compressed_stream_file_writer->write( - reinterpret_cast(m_compressed_stream_block.dst), + static_cast(m_compressed_stream_block.dst), m_compressed_stream_block.pos ); } @@ -96,14 +97,14 @@ void Compressor::write(char const* data, size_t data_length) { m_uncompressed_stream_pos += data_length; } -void Compressor::flush() { +auto Compressor::flush() -> void { if (false == m_compression_stream_contains_data) { return; } m_compressed_stream_block.pos = 0; - auto end_stream_result = ZSTD_endStream(m_compression_stream, &m_compressed_stream_block); - if (end_stream_result) { + auto const end_stream_result{ZSTD_endStream(m_compression_stream, &m_compressed_stream_block)}; + if (0 != ZSTD_isError(end_stream_result)) { // Note: Output buffer is large enough that it is guaranteed to have enough room to be // able to flush the entire buffer, so this can only be an error SPDLOG_ERROR( @@ -113,14 +114,14 @@ void Compressor::flush() { throw OperationFailed(ErrorCode_Failure, __FILENAME__, __LINE__); } m_compressed_stream_file_writer->write( - reinterpret_cast(m_compressed_stream_block.dst), + static_cast(m_compressed_stream_block.dst), m_compressed_stream_block.pos ); m_compression_stream_contains_data = false; } -ErrorCode Compressor::try_get_pos(size_t& pos) const { +auto Compressor::try_get_pos(size_t& pos) const -> ErrorCode { if (nullptr == m_compressed_stream_file_writer) { return ErrorCode_NotInit; } @@ -129,28 +130,28 @@ ErrorCode Compressor::try_get_pos(size_t& pos) const { return ErrorCode_Success; } -void Compressor::flush_without_ending_frame() { +auto Compressor::flush_without_ending_frame() -> void { if (false == m_compression_stream_contains_data) { return; } while (true) { m_compressed_stream_block.pos = 0; - auto result = ZSTD_flushStream(m_compression_stream, &m_compressed_stream_block); - if (ZSTD_isError(result)) { + auto const flush_result{ZSTD_flushStream(m_compression_stream, &m_compressed_stream_block)}; + if (0 != ZSTD_isError(flush_result)) { SPDLOG_ERROR( - "streaming_compression::zstd::Compressor: ZSTD_compressStream2() error: {}", - ZSTD_getErrorName(result) + "streaming_compression::zstd::Compressor: ZSTD_flushStream() error: {}", + ZSTD_getErrorName(flush_result) ); throw OperationFailed(ErrorCode_Failure, __FILENAME__, __LINE__); } - if (m_compressed_stream_block.pos) { + if (m_compressed_stream_block.pos > 0) { m_compressed_stream_file_writer->write( - reinterpret_cast(m_compressed_stream_block.dst), + static_cast(m_compressed_stream_block.dst), m_compressed_stream_block.pos ); } - if (0 == result) { + if (0 == flush_result) { break; } } diff --git a/components/core/src/clp/streaming_compression/zstd/Compressor.hpp b/components/core/src/clp/streaming_compression/zstd/Compressor.hpp index 75971dfa8..f55275f3e 100644 --- a/components/core/src/clp/streaming_compression/zstd/Compressor.hpp +++ b/components/core/src/clp/streaming_compression/zstd/Compressor.hpp @@ -1,12 +1,12 @@ #ifndef CLP_STREAMING_COMPRESSION_ZSTD_COMPRESSOR_HPP #define CLP_STREAMING_COMPRESSION_ZSTD_COMPRESSOR_HPP -#include -#include +#include #include -#include +#include "../../Array.hpp" +#include "../../ErrorCode.hpp" #include "../../FileWriter.hpp" #include "../../TraceableException.hpp" #include "../Compressor.hpp" @@ -20,11 +20,12 @@ class Compressor : public ::clp::streaming_compression::Compressor { public: // Constructors OperationFailed(ErrorCode error_code, char const* const filename, int line_number) - : TraceableException(error_code, filename, line_number) {} + : TraceableException{error_code, filename, line_number} {} // Methods - char const* what() const noexcept override { - return "streaming_compression::zstd::Compressor operation failed"; + [[nodiscard]] auto what() const noexcept -> char const* override { + return "streaming_compression::zstd::Compressor " + "operation failed"; } }; @@ -32,11 +33,15 @@ class Compressor : public ::clp::streaming_compression::Compressor { Compressor(); // Destructor - ~Compressor(); + ~Compressor() override; - // Explicitly disable copy and move constructor/assignment + // Delete copy constructor and assignment operator Compressor(Compressor const&) = delete; - Compressor& operator=(Compressor const&) = delete; + auto operator=(Compressor const&) -> Compressor& = delete; + + // Default move constructor and assignment operator + Compressor(Compressor&&) noexcept = default; + auto operator=(Compressor&&) noexcept -> Compressor& = default; // Methods implementing the WriterInterface /** @@ -44,11 +49,12 @@ class Compressor : public ::clp::streaming_compression::Compressor { * @param data * @param data_length */ - void write(char const* data, size_t data_length) override; + auto write(char const* data, size_t data_length) -> void override; + /** * Writes any internally buffered data to file and ends the current frame */ - void flush() override; + auto flush() -> void override; /** * Tries to get the current position of the write head @@ -56,39 +62,46 @@ class Compressor : public ::clp::streaming_compression::Compressor { * @return ErrorCode_NotInit if the compressor is not open * @return ErrorCode_Success on success */ - ErrorCode try_get_pos(size_t& pos) const override; + [[nodiscard]] auto try_get_pos(size_t& pos) const -> ErrorCode override; // Methods implementing the Compressor interface /** * Closes the compressor */ - void close() override; + auto close() -> void override; + + /** + * Initializes the compression stream with the default compression level + * @param file_writer + */ + auto open(FileWriter& file_writer) -> void override { + this->open(file_writer, cDefaultCompressionLevel); + } - // Methods /** - * Initialize streaming compressor + * Initializes the compression stream with the given compression level * @param file_writer * @param compression_level */ - void open(FileWriter& file_writer, int compression_level = cDefaultCompressionLevel); + auto open(FileWriter& file_writer, int compression_level) -> void; /** * Flushes the stream without ending the current frame */ - void flush_without_ending_frame(); + auto flush_without_ending_frame() -> void; private: // Variables - FileWriter* m_compressed_stream_file_writer; + FileWriter* m_compressed_stream_file_writer{nullptr}; // Compressed stream variables - ZSTD_CStream* m_compression_stream; - bool m_compression_stream_contains_data; + ZSTD_CStream* m_compression_stream{ZSTD_createCStream()}; + bool m_compression_stream_contains_data{false}; + Array m_compressed_stream_block_buffer{ZSTD_CStreamOutSize()}; ZSTD_outBuffer m_compressed_stream_block; - std::unique_ptr m_compressed_stream_block_buffer; - size_t m_uncompressed_stream_pos; + size_t m_uncompressed_stream_pos{0}; }; } // namespace clp::streaming_compression::zstd diff --git a/components/core/src/clp/streaming_compression/zstd/Constants.hpp b/components/core/src/clp/streaming_compression/zstd/Constants.hpp index a0e57e3e1..153478377 100644 --- a/components/core/src/clp/streaming_compression/zstd/Constants.hpp +++ b/components/core/src/clp/streaming_compression/zstd/Constants.hpp @@ -1,11 +1,8 @@ #ifndef CLP_STREAMING_COMPRESSION_ZSTD_CONSTANTS_HPP #define CLP_STREAMING_COMPRESSION_ZSTD_CONSTANTS_HPP -#include -#include - namespace clp::streaming_compression::zstd { -constexpr int cDefaultCompressionLevel = 3; +constexpr int cDefaultCompressionLevel{3}; } // namespace clp::streaming_compression::zstd #endif // CLP_STREAMING_COMPRESSION_ZSTD_CONSTANTS_HPP diff --git a/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp b/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp index 9f320efe6..818379a24 100644 --- a/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp +++ b/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp @@ -2,9 +2,8 @@ #include -#include - #include "../../Defs.h" +#include "../../ReadOnlyMemoryMappedFile.hpp" #include "../../spdlog_with_specializations.hpp" namespace clp::streaming_compression::zstd { @@ -182,10 +181,7 @@ void Decompressor::open(FileReader& file_reader, size_t file_read_buffer_capacit void Decompressor::close() { switch (m_input_type) { case InputType::MemoryMappedCompressedFile: - if (m_memory_mapped_compressed_file.is_open()) { - // An existing file is memory mapped by the decompressor - m_memory_mapped_compressed_file.close(); - } + m_memory_mapped_file.reset(); break; case InputType::File: m_file_read_buffer.reset(); @@ -209,40 +205,12 @@ ErrorCode Decompressor::open(std::string const& compressed_file_path) { } m_input_type = InputType::MemoryMappedCompressedFile; - // Create memory mapping for compressed_file_path, use boost read only - // memory mapped file - boost::system::error_code boost_error_code; - size_t compressed_file_size - = boost::filesystem::file_size(compressed_file_path, boost_error_code); - if (boost_error_code) { - SPDLOG_ERROR( - "streaming_compression::zstd::Decompressor: Unable to obtain file size for " - "'{}' - {}.", - compressed_file_path.c_str(), - boost_error_code.message().c_str() - ); - return ErrorCode_Failure; - } - - boost::iostreams::mapped_file_params memory_map_params; - memory_map_params.path = compressed_file_path; - memory_map_params.flags = boost::iostreams::mapped_file::readonly; - memory_map_params.length = compressed_file_size; - // Try to map it to the same memory location as previous memory mapped - // file - memory_map_params.hint = m_memory_mapped_compressed_file.data(); - m_memory_mapped_compressed_file.open(memory_map_params); - if (!m_memory_mapped_compressed_file.is_open()) { - SPDLOG_ERROR( - "streaming_compression::zstd::Decompressor: Unable to memory map the " - "compressed file with path: {}", - compressed_file_path.c_str() - ); - return ErrorCode_Failure; - } + // Create read-only memory mapping for compressed_file_path + m_memory_mapped_file = std::make_unique(compressed_file_path); + auto const file_view{m_memory_mapped_file->get_view()}; // Configure input stream - m_compressed_stream_block = {m_memory_mapped_compressed_file.data(), compressed_file_size, 0}; + m_compressed_stream_block = {file_view.data(), file_view.size(), 0}; reset_stream(); diff --git a/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp b/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp index 665674373..cc9e90fe4 100644 --- a/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp +++ b/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp @@ -4,10 +4,10 @@ #include #include -#include #include #include "../../FileReader.hpp" +#include "../../ReadOnlyMemoryMappedFile.hpp" #include "../../TraceableException.hpp" #include "../Decompressor.hpp" @@ -125,7 +125,7 @@ class Decompressor : public ::clp::streaming_compression::Decompressor { // Compressed stream variables ZSTD_DStream* m_decompression_stream; - boost::iostreams::mapped_file_source m_memory_mapped_compressed_file; + std::unique_ptr m_memory_mapped_file; FileReader* m_file_reader; size_t m_file_reader_initial_pos; std::unique_ptr m_file_read_buffer; diff --git a/components/core/src/clp/type_utils.hpp b/components/core/src/clp/type_utils.hpp index 11a3b784e..44cfcc521 100644 --- a/components/core/src/clp/type_utils.hpp +++ b/components/core/src/clp/type_utils.hpp @@ -2,7 +2,9 @@ #define CLP_TYPE_UTILS_HPP #include +#include #include +#include namespace clp { /** @@ -67,6 +69,42 @@ std::enable_if_t size_checked_pointer_cast(Source* src) { return reinterpret_cast(src); } + +/** + * Template that converts a tuple of types into a variant. + * @tparam Tuple A tuple of types + */ +template +struct tuple_to_variant; + +template +struct tuple_to_variant> { + using Type = std::variant; +}; + +/** + * Template to validate if the given type is in the given tuple of types. + * @tparam Type + * @tparam Tuple + */ +template +struct is_in_type_tuple; + +template +struct is_in_type_tuple> + : std::disjunction...> {}; + +/** + * Concept for integer types. + */ +template +concept IntegerType = std::is_integral_v && false == std::is_same_v; + +/** + * Concept for signed integer types. + */ +template +concept SignedIntegerType = IntegerType && std::is_signed_v; } // namespace clp #endif // CLP_TYPE_UTILS_HPP diff --git a/components/core/src/clp/utf8_utils.cpp b/components/core/src/clp/utf8_utils.cpp new file mode 100644 index 000000000..06fafd659 --- /dev/null +++ b/components/core/src/clp/utf8_utils.cpp @@ -0,0 +1,55 @@ +#include "utf8_utils.hpp" + +#include +#include +#include + +namespace clp { +auto is_utf8_encoded(std::string_view str) -> bool { + auto escape_handler = []([[maybe_unused]] std::string_view::const_iterator it) -> void {}; + return validate_utf8_string(str, escape_handler); +} + +namespace utf8_utils_internal { +auto parse_and_validate_lead_byte( + uint8_t byte, + size_t& num_continuation_bytes, + uint32_t& code_point, + uint32_t& code_point_lower_bound, + uint32_t& code_point_upper_bound +) -> bool { + if ((byte & cFourByteUtf8CharHeaderMask) == cFourByteUtf8CharHeader) { + num_continuation_bytes = 3; + code_point = (~cFourByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cFourByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cFourByteUtf8CharCodePointUpperBound; + } else if ((byte & cThreeByteUtf8CharHeaderMask) == cThreeByteUtf8CharHeader) { + num_continuation_bytes = 2; + code_point = (~cThreeByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cThreeByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cThreeByteUtf8CharCodePointUpperBound; + } else if ((byte & cTwoByteUtf8CharHeaderMask) == cTwoByteUtf8CharHeader) { + num_continuation_bytes = 1; + code_point = (~cTwoByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cTwoByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cTwoByteUtf8CharCodePointUpperBound; + } else { + return false; + } + return true; +} + +auto is_ascii_char(uint8_t byte) -> bool { + return cOneByteUtf8CharCodePointUpperBound >= byte; +} + +auto is_valid_utf8_continuation_byte(uint8_t byte) -> bool { + return (byte & cUtf8ContinuationByteMask) == cUtf8ContinuationByteHeader; +} + +auto parse_continuation_byte(uint32_t code_point, uint8_t continuation_byte) -> uint32_t { + return (code_point << cUtf8NumContinuationByteCodePointBits) + + (continuation_byte & cUtf8ContinuationByteCodePointMask); +} +} // namespace utf8_utils_internal +} // namespace clp diff --git a/components/core/src/clp/utf8_utils.hpp b/components/core/src/clp/utf8_utils.hpp new file mode 100644 index 000000000..fe9569b00 --- /dev/null +++ b/components/core/src/clp/utf8_utils.hpp @@ -0,0 +1,144 @@ +#ifndef CLP_UTF8_UTILS_HPP +#define CLP_UTF8_UTILS_HPP + +#include +#include +#include + +namespace clp { +// Constants +// Lead byte signature +constexpr uint8_t cTwoByteUtf8CharHeaderMask{0xE0}; // 0b111x_xxxx +constexpr uint8_t cTwoByteUtf8CharHeader{0xC0}; // 0b110x_xxxx +constexpr uint8_t cThreeByteUtf8CharHeaderMask{0xF0}; // 0b1111_xxxx +constexpr uint8_t cThreeByteUtf8CharHeader{0xE0}; // 0b1110_xxxx +constexpr uint8_t cFourByteUtf8CharHeaderMask{0xF8}; // 0b1111_1xxx +constexpr uint8_t cFourByteUtf8CharHeader{0xF0}; // 0b1111_0xxx + +// Code point ranges (inclusive) +constexpr uint32_t cOneByteUtf8CharCodePointLowerBound{0}; +constexpr uint32_t cOneByteUtf8CharCodePointUpperBound{0x7F}; +constexpr uint32_t cTwoByteUtf8CharCodePointLowerBound{0x80}; +constexpr uint32_t cTwoByteUtf8CharCodePointUpperBound{0x7FF}; +constexpr uint32_t cThreeByteUtf8CharCodePointLowerBound{0x800}; +constexpr uint32_t cThreeByteUtf8CharCodePointUpperBound{0xFFFF}; +constexpr uint32_t cFourByteUtf8CharCodePointLowerBound{0x1'0000}; +constexpr uint32_t cFourByteUtf8CharCodePointUpperBound{0x10'FFFF}; + +// Continuation byte +constexpr uint32_t cUtf8ContinuationByteMask{0xC0}; +constexpr uint32_t cUtf8ContinuationByteHeader{0x80}; +constexpr uint32_t cUtf8ContinuationByteCodePointMask{0x3F}; +constexpr uint8_t cUtf8NumContinuationByteCodePointBits{6}; + +/** + * Validates whether the given string is UTF-8 encoded, optionally escaping ASCII characters using + * the given handler. + * @tparam EscapeHandler Method to optionally escape any ASCII character in the string. + * @param src + * @param escape_handler + * @return Whether the input is a valid UTF-8 encoded string. + */ +template +requires std::is_invocable_v +[[nodiscard]] auto validate_utf8_string(std::string_view src, EscapeHandler escape_handler) -> bool; + +/** + * @param str + * @return Whether the input is a valid UTF-8 encoded string. + */ +[[nodiscard]] auto is_utf8_encoded(std::string_view str) -> bool; + +namespace utf8_utils_internal { +/** + * Validates whether the given byte is a valid lead byte for a multi-byte UTF-8 character, parses + * the byte, and returns the parsed properties as well as associated properties. + * @param byte Byte to validate. + * @param num_continuation_bytes Returns the number of continuation bytes expected. + * @param code_point Returns the code point bits parsed from the lead byte. + * @param code_point_lower_bound Returns the lower bound of the code point range for the UTF-8 + * character. + * @param code_point_upper_bound Returns the upper bound of the code point range for the UTF-8 + * character. + * @return Whether the input byte is a valid lead byte for a multi-byte UTF-8 character. + */ +[[nodiscard]] auto parse_and_validate_lead_byte( + uint8_t byte, + size_t& num_continuation_bytes, + uint32_t& code_point, + uint32_t& code_point_lower_bound, + uint32_t& code_point_upper_bound +) -> bool; + +/** + * @param byte + * @return Whether the given byte is a valid ASCII character. + */ +[[nodiscard]] auto is_ascii_char(uint8_t byte) -> bool; + +/* + * @param byte + * @return Whether the input byte is a valid UTF-8 continuation byte. + */ +[[nodiscard]] auto is_valid_utf8_continuation_byte(uint8_t byte) -> bool; + +/** + * Parses the code-point bits from the given continuation byte and combines them with the given + * code point. + * @param code_point + * @param continuation_byte + * @return The updated code point. + */ +[[nodiscard]] auto +parse_continuation_byte(uint32_t code_point, uint8_t continuation_byte) -> uint32_t; +} // namespace utf8_utils_internal + +template +requires std::is_invocable_v +auto validate_utf8_string(std::string_view src, EscapeHandler escape_handler) -> bool { + size_t num_continuation_bytes_to_validate{0}; + uint32_t code_point{}; + uint32_t code_point_lower_bound{}; + uint32_t code_point_upper_bound{}; + + // NOLINTNEXTLINE(readability-qualified-auto) + for (auto it{src.cbegin()}; it != src.cend(); ++it) { + auto const byte{static_cast(*it)}; + if (0 == num_continuation_bytes_to_validate) { + if (utf8_utils_internal::is_ascii_char(byte)) { + escape_handler(it); + } else if (false + == utf8_utils_internal::parse_and_validate_lead_byte( + byte, + num_continuation_bytes_to_validate, + code_point, + code_point_lower_bound, + code_point_upper_bound + )) + { + return false; + } + } else { + if (false == utf8_utils_internal::is_valid_utf8_continuation_byte(byte)) { + return false; + } + code_point = utf8_utils_internal::parse_continuation_byte(code_point, byte); + --num_continuation_bytes_to_validate; + if (0 == num_continuation_bytes_to_validate + && (code_point < code_point_lower_bound || code_point_upper_bound < code_point)) + { + return false; + } + } + } + + if (0 != num_continuation_bytes_to_validate) { + // Incomplete UTF-8 character + return false; + } + + return true; +} +} // namespace clp + +#endif // CLP_UTF8_UTILS_HPP diff --git a/components/core/src/clp_s/ArchiveReader.cpp b/components/core/src/clp_s/ArchiveReader.cpp index 93f905e3b..7c68b301d 100644 --- a/components/core/src/clp_s/ArchiveReader.cpp +++ b/components/core/src/clp_s/ArchiveReader.cpp @@ -1,26 +1,36 @@ #include "ArchiveReader.hpp" +#include +#include + #include "archive_constants.hpp" #include "ReaderUtils.hpp" +using std::string_view; + namespace clp_s { -void ArchiveReader::open(std::string const& archive_path) { +void ArchiveReader::open(string_view archives_dir, string_view archive_id) { if (m_is_open) { throw OperationFailed(ErrorCodeNotReady, __FILENAME__, __LINE__); } m_is_open = true; - m_archive_path = archive_path; + m_archive_id = archive_id; + std::filesystem::path archive_path{archives_dir}; + archive_path /= m_archive_id; + auto const archive_path_str = archive_path.string(); + + m_var_dict = ReaderUtils::get_variable_dictionary_reader(archive_path_str); + m_log_dict = ReaderUtils::get_log_type_dictionary_reader(archive_path_str); + m_array_dict = ReaderUtils::get_array_dictionary_reader(archive_path_str); + m_timestamp_dict = ReaderUtils::get_timestamp_dictionary_reader(archive_path_str); - m_var_dict = ReaderUtils::get_variable_dictionary_reader(m_archive_path); - m_log_dict = ReaderUtils::get_log_type_dictionary_reader(m_archive_path); - m_array_dict = ReaderUtils::get_array_dictionary_reader(m_archive_path); - m_timestamp_dict = ReaderUtils::get_timestamp_dictionary_reader(m_archive_path); + m_schema_tree = ReaderUtils::read_schema_tree(archive_path_str); + m_schema_map = ReaderUtils::read_schemas(archive_path_str); - m_schema_tree = ReaderUtils::read_schema_tree(m_archive_path); - m_schema_map = ReaderUtils::read_schemas(m_archive_path); + m_log_event_idx_column_id = m_schema_tree->get_metadata_field_id(constants::cLogEventIdxName); - m_tables_file_reader.open(m_archive_path + constants::cArchiveTablesFile); - m_table_metadata_file_reader.open(m_archive_path + constants::cArchiveTableMetadataFile); + m_table_metadata_file_reader.open(archive_path_str + constants::cArchiveTableMetadataFile); + m_stream_reader.open_packed_streams(archive_path_str + constants::cArchiveTablesFile); } void ArchiveReader::read_metadata() { @@ -30,6 +40,20 @@ void ArchiveReader::read_metadata() { cDecompressorFileReadBufferCapacity ); + m_stream_reader.read_metadata(m_table_metadata_decompressor); + + size_t num_separate_column_schemas; + if (auto error + = m_table_metadata_decompressor.try_read_numeric_value(num_separate_column_schemas); + ErrorCodeSuccess != error) + { + throw OperationFailed(error, __FILENAME__, __LINE__); + } + + if (0 != num_separate_column_schemas) { + throw OperationFailed(ErrorCode::ErrorCodeUnsupported, __FILENAME__, __LINE__); + } + size_t num_schemas; if (auto error = m_table_metadata_decompressor.try_read_numeric_value(num_schemas); ErrorCodeSuccess != error) @@ -37,39 +61,65 @@ void ArchiveReader::read_metadata() { throw OperationFailed(error, __FILENAME__, __LINE__); } - for (size_t i = 0; i < num_schemas; i++) { + bool prev_metadata_initialized{false}; + SchemaReader::SchemaMetadata prev_metadata{}; + int32_t prev_schema_id{}; + for (size_t i = 0; i < num_schemas; ++i) { + uint64_t stream_id; + uint64_t stream_offset; int32_t schema_id; uint64_t num_messages; - size_t table_offset; - size_t uncompressed_size; - if (auto error = m_table_metadata_decompressor.try_read_numeric_value(schema_id); + if (auto error = m_table_metadata_decompressor.try_read_numeric_value(stream_id); ErrorCodeSuccess != error) { throw OperationFailed(error, __FILENAME__, __LINE__); } - if (auto error = m_table_metadata_decompressor.try_read_numeric_value(num_messages); + if (auto error = m_table_metadata_decompressor.try_read_numeric_value(stream_offset); ErrorCodeSuccess != error) { throw OperationFailed(error, __FILENAME__, __LINE__); } - if (auto error = m_table_metadata_decompressor.try_read_numeric_value(table_offset); + if (stream_offset > m_stream_reader.get_uncompressed_stream_size(stream_id)) { + throw OperationFailed(ErrorCodeCorrupt, __FILENAME__, __LINE__); + } + + if (auto error = m_table_metadata_decompressor.try_read_numeric_value(schema_id); ErrorCodeSuccess != error) { throw OperationFailed(error, __FILENAME__, __LINE__); } - if (auto error = m_table_metadata_decompressor.try_read_numeric_value(uncompressed_size); + if (auto error = m_table_metadata_decompressor.try_read_numeric_value(num_messages); ErrorCodeSuccess != error) { throw OperationFailed(error, __FILENAME__, __LINE__); } - m_id_to_table_metadata[schema_id] = {num_messages, table_offset, uncompressed_size}; + if (prev_metadata_initialized) { + uint64_t uncompressed_size{0}; + if (stream_id != prev_metadata.stream_id) { + uncompressed_size + = m_stream_reader.get_uncompressed_stream_size(prev_metadata.stream_id) + - prev_metadata.stream_offset; + } else { + uncompressed_size = stream_offset - prev_metadata.stream_offset; + } + prev_metadata.uncompressed_size = uncompressed_size; + m_id_to_schema_metadata[prev_schema_id] = prev_metadata; + } else { + prev_metadata_initialized = true; + } + prev_metadata = {stream_id, stream_offset, num_messages, 0}; + prev_schema_id = schema_id; m_schema_ids.push_back(schema_id); } + prev_metadata.uncompressed_size + = m_stream_reader.get_uncompressed_stream_size(prev_metadata.stream_id) + - prev_metadata.stream_offset; + m_id_to_schema_metadata[prev_schema_id] = prev_metadata; m_table_metadata_decompressor.close(); } @@ -81,25 +131,45 @@ void ArchiveReader::read_dictionaries_and_metadata() { read_metadata(); } -SchemaReader& ArchiveReader::read_table( +SchemaReader& ArchiveReader::read_schema_table( int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records ) { - constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB - - if (m_id_to_table_metadata.count(schema_id) == 0) { + if (m_id_to_schema_metadata.count(schema_id) == 0) { throw OperationFailed(ErrorCodeFileNotFound, __FILENAME__, __LINE__); } - auto& schema_reader - = create_schema_reader(schema_id, should_extract_timestamp, should_marshal_records); + initialize_schema_reader( + m_schema_reader, + schema_id, + should_extract_timestamp, + should_marshal_records + ); + + auto& schema_metadata = m_id_to_schema_metadata[schema_id]; + auto stream_buffer = read_stream(schema_metadata.stream_id, true); + m_schema_reader + .load(stream_buffer, schema_metadata.stream_offset, schema_metadata.uncompressed_size); + return m_schema_reader; +} - m_tables_file_reader.try_seek_from_begin(m_id_to_table_metadata[schema_id].offset); - m_tables_decompressor.open(m_tables_file_reader, cDecompressorFileReadBufferCapacity); - schema_reader.load(m_tables_decompressor, m_id_to_table_metadata[schema_id].uncompressed_size); - m_tables_decompressor.close_for_reuse(); - return schema_reader; +std::vector> ArchiveReader::read_all_tables() { + std::vector> readers; + readers.reserve(m_id_to_schema_metadata.size()); + for (auto schema_id : m_schema_ids) { + auto schema_reader = std::make_shared(); + initialize_schema_reader(*schema_reader, schema_id, true, true); + auto& schema_metadata = m_id_to_schema_metadata[schema_id]; + auto stream_buffer = read_stream(schema_metadata.stream_id, false); + schema_reader->load( + stream_buffer, + schema_metadata.stream_offset, + schema_metadata.uncompressed_size + ); + readers.push_back(std::move(schema_reader)); + } + return readers; } BaseColumnReader* ArchiveReader::append_reader_column(SchemaReader& reader, int32_t column_id) { @@ -192,17 +262,19 @@ void ArchiveReader::append_unordered_reader_columns( } } -SchemaReader& ArchiveReader::create_schema_reader( +void ArchiveReader::initialize_schema_reader( + SchemaReader& reader, int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records ) { auto& schema = (*m_schema_map)[schema_id]; - m_schema_reader.reset( + reader.reset( m_schema_tree, + m_projection, schema_id, schema.get_ordered_schema_view(), - m_id_to_table_metadata[schema_id].num_messages, + m_id_to_schema_metadata[schema_id].num_messages, should_marshal_records ); auto timestamp_column_ids = m_timestamp_dict->get_authoritative_timestamp_column_ids(); @@ -218,7 +290,7 @@ SchemaReader& ArchiveReader::create_schema_reader( Schema::get_unordered_object_type(column_id) ); append_unordered_reader_columns( - m_schema_reader, + reader, mst_subtree_root_node_id, sub_schema, should_marshal_records @@ -231,28 +303,32 @@ SchemaReader& ArchiveReader::create_schema_reader( // column id is the root of the unordered object, so we can pass it directly to // append_unordered_reader_columns. append_unordered_reader_columns( - m_schema_reader, + reader, column_id, std::span(), should_marshal_records ); continue; } - BaseColumnReader* column_reader = append_reader_column(m_schema_reader, column_id); + BaseColumnReader* column_reader = append_reader_column(reader, column_id); + + if (column_id == m_log_event_idx_column_id + && nullptr != dynamic_cast(column_reader)) + { + reader.mark_column_as_log_event_idx(static_cast(column_reader)); + } if (should_extract_timestamp && column_reader && timestamp_column_ids.count(column_id) > 0) { - m_schema_reader.mark_column_as_timestamp(column_reader); + reader.mark_column_as_timestamp(column_reader); } } - return m_schema_reader; } void ArchiveReader::store(FileWriter& writer) { std::string message; - - for (auto& [id, table_metadata] : m_id_to_table_metadata) { - auto& schema_reader = read_table(id, false, true); + for (auto schema_id : m_schema_ids) { + auto& schema_reader = read_schema_table(schema_id, false, true); while (schema_reader.get_next_message(message)) { writer.write(message.c_str(), message.length()); } @@ -270,11 +346,29 @@ void ArchiveReader::close() { m_array_dict->close(); m_timestamp_dict->close(); - m_tables_file_reader.close(); + m_stream_reader.close(); m_table_metadata_file_reader.close(); - m_id_to_table_metadata.clear(); + m_id_to_schema_metadata.clear(); m_schema_ids.clear(); + m_cur_stream_id = 0; + m_stream_buffer.reset(); + m_stream_buffer_size = 0ULL; + m_log_event_idx_column_id = -1; } +std::shared_ptr ArchiveReader::read_stream(size_t stream_id, bool reuse_buffer) { + if (nullptr != m_stream_buffer && m_cur_stream_id == stream_id) { + return m_stream_buffer; + } + + if (false == reuse_buffer) { + m_stream_buffer.reset(); + m_stream_buffer_size = 0; + } + + m_stream_reader.read_stream(stream_id, m_stream_buffer, m_stream_buffer_size); + m_cur_stream_id = stream_id; + return m_stream_buffer; +} } // namespace clp_s diff --git a/components/core/src/clp_s/ArchiveReader.hpp b/components/core/src/clp_s/ArchiveReader.hpp index 54eb42698..6b437dfd2 100644 --- a/components/core/src/clp_s/ArchiveReader.hpp +++ b/components/core/src/clp_s/ArchiveReader.hpp @@ -4,13 +4,16 @@ #include #include #include +#include #include #include #include "DictionaryReader.hpp" +#include "PackedStreamReader.hpp" #include "ReaderUtils.hpp" #include "SchemaReader.hpp" +#include "search/Projection.hpp" #include "TimestampDictionaryReader.hpp" #include "Utils.hpp" @@ -29,9 +32,10 @@ class ArchiveReader { /** * Opens an archive for reading. - * @param archive_path + * @param archives_dir + * @param archive_id */ - void open(std::string const& archive_path); + void open(std::string_view archives_dir, std::string_view archive_id); /** * Reads the dictionaries and metadata. @@ -89,8 +93,19 @@ class ArchiveReader { * @param should_marshal_records * @return the schema reader */ - SchemaReader& - read_table(int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records); + SchemaReader& read_schema_table( + int32_t schema_id, + bool should_extract_timestamp, + bool should_marshal_records + ); + + /** + * Loads all of the tables in the archive and returns SchemaReaders for them. + * @return the schema readers for every table in the archive + */ + std::vector> read_all_tables(); + + std::string_view get_archive_id() { return m_archive_id; } std::shared_ptr get_variable_dictionary() { return m_var_dict; } @@ -123,15 +138,25 @@ class ArchiveReader { */ [[nodiscard]] std::vector const& get_schema_ids() const { return m_schema_ids; } + void set_projection(std::shared_ptr projection) { + m_projection = projection; + } + + /** + * @return true if this archive has log ordering information, and false otherwise. + */ + bool has_log_order() { return m_log_event_idx_column_id >= 0; } + private: /** - * Creates a schema reader for a given schema. + * Initializes a schema reader passed by reference to become a reader for a given schema. + * @param reader * @param schema_id * @param should_extract_timestamp * @param should_marshal_records - * @return a reference to the newly created schema reader initialized with the given parameters */ - SchemaReader& create_schema_reader( + void initialize_schema_reader( + SchemaReader& reader, int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records @@ -160,9 +185,20 @@ class ArchiveReader { bool should_marshal_records ); - bool m_is_open; - std::string m_archive_path; + /** + * Reads a table with given ID from the packed stream reader. If read_stream is called multiple + * times in a row for the same stream_id a cached buffer is returned. This function allows the + * caller to ask for the same buffer to be reused to read multiple different tables: this can + * save memory allocations, but can only be used when tables are read one at a time. + * @param stream_id + * @param reuse_buffer when true the same buffer is reused across invocations, overwriting data + * returned previous calls to read_stream + * @return a buffer containing the decompressed stream identified by stream_id + */ + std::shared_ptr read_stream(size_t stream_id, bool reuse_buffer); + bool m_is_open; + std::string m_archive_id; std::shared_ptr m_var_dict; std::shared_ptr m_log_dict; std::shared_ptr m_array_dict; @@ -171,13 +207,19 @@ class ArchiveReader { std::shared_ptr m_schema_tree; std::shared_ptr m_schema_map; std::vector m_schema_ids; - std::map m_id_to_table_metadata; + std::map m_id_to_schema_metadata; + std::shared_ptr m_projection{ + std::make_shared(search::ProjectionMode::ReturnAllColumns) + }; - FileReader m_tables_file_reader; + PackedStreamReader m_stream_reader; FileReader m_table_metadata_file_reader; - ZstdDecompressor m_tables_decompressor; ZstdDecompressor m_table_metadata_decompressor; SchemaReader m_schema_reader; + std::shared_ptr m_stream_buffer{}; + size_t m_stream_buffer_size{0ULL}; + size_t m_cur_stream_id{0ULL}; + int32_t m_log_event_idx_column_id{-1}; }; } // namespace clp_s diff --git a/components/core/src/clp_s/ArchiveWriter.cpp b/components/core/src/clp_s/ArchiveWriter.cpp index ba540a79d..d627479de 100644 --- a/components/core/src/clp_s/ArchiveWriter.cpp +++ b/components/core/src/clp_s/ArchiveWriter.cpp @@ -1,5 +1,9 @@ #include "ArchiveWriter.hpp" +#include +#include +#include + #include #include "archive_constants.hpp" @@ -11,17 +15,23 @@ void ArchiveWriter::open(ArchiveWriterOption const& option) { m_id = boost::uuids::to_string(option.id); m_compression_level = option.compression_level; m_print_archive_stats = option.print_archive_stats; - auto archive_path = boost::filesystem::path(option.archives_dir) / m_id; + m_single_file_archive = option.single_file_archive; + m_min_table_size = option.min_table_size; + m_archives_dir = option.archives_dir; + std::string working_dir_name = m_id; + if (option.single_file_archive) { + working_dir_name += constants::cTmpPostfix; + } + auto archive_path = std::filesystem::path(option.archives_dir) / working_dir_name; - boost::system::error_code boost_error_code; - bool path_exists = boost::filesystem::exists(archive_path, boost_error_code); - if (path_exists) { + std::error_code ec; + if (std::filesystem::exists(archive_path, ec)) { SPDLOG_ERROR("Archive path already exists: {}", archive_path.c_str()); throw OperationFailed(ErrorCodeUnsupported, __FILENAME__, __LINE__); } m_archive_path = archive_path.string(); - if (false == boost::filesystem::create_directory(m_archive_path)) { + if (false == std::filesystem::create_directory(m_archive_path, ec)) { throw OperationFailed(ErrorCodeErrno, __FILENAME__, __LINE__); } @@ -36,20 +46,42 @@ void ArchiveWriter::open(ArchiveWriterOption const& option) { std::string array_dict_path = m_archive_path + constants::cArchiveArrayDictFile; m_array_dict = std::make_shared(); m_array_dict->open(array_dict_path, m_compression_level, UINT64_MAX); - - std::string timestamp_dict_path = m_archive_path + constants::cArchiveTimestampDictFile; - m_timestamp_dict = std::make_shared(); - m_timestamp_dict->open(timestamp_dict_path, m_compression_level); } void ArchiveWriter::close() { - m_compressed_size += m_var_dict->close(); - m_compressed_size += m_log_dict->close(); - m_compressed_size += m_array_dict->close(); - m_compressed_size += m_timestamp_dict->close(); - m_compressed_size += m_schema_tree.store(m_archive_path, m_compression_level); - m_compressed_size += m_schema_map.store(m_archive_path, m_compression_level); - m_compressed_size += store_tables(); + auto var_dict_compressed_size = m_var_dict->close(); + auto log_dict_compressed_size = m_log_dict->close(); + auto array_dict_compressed_size = m_array_dict->close(); + auto schema_tree_compressed_size = m_schema_tree.store(m_archive_path, m_compression_level); + auto schema_map_compressed_size = m_schema_map.store(m_archive_path, m_compression_level); + auto [table_metadata_compressed_size, table_compressed_size] = store_tables(); + + if (m_single_file_archive) { + std::vector files{ + {constants::cArchiveSchemaTreeFile, schema_tree_compressed_size}, + {constants::cArchiveSchemaMapFile, schema_map_compressed_size}, + {constants::cArchiveTableMetadataFile, table_metadata_compressed_size}, + {constants::cArchiveVarDictFile, var_dict_compressed_size}, + {constants::cArchiveLogDictFile, log_dict_compressed_size}, + {constants::cArchiveArrayDictFile, array_dict_compressed_size}, + {constants::cArchiveTablesFile, table_compressed_size} + }; + uint64_t offset = 0; + for (auto& file : files) { + uint64_t original_size = file.o; + file.o = offset; + offset += original_size; + } + write_single_file_archive(files); + } else { + // Timestamp dictionary written separately here until we transition to moving it inside of + // the metadata region of multi-file archives. + auto timestamp_dict_compressed_size = write_timestamp_dict(); + m_compressed_size = var_dict_compressed_size + log_dict_compressed_size + + array_dict_compressed_size + timestamp_dict_compressed_size + + schema_tree_compressed_size + schema_map_compressed_size + + table_metadata_compressed_size + table_compressed_size; + } if (m_metadata_db) { update_metadata_db(); @@ -62,9 +94,128 @@ void ArchiveWriter::close() { m_id_to_schema_writer.clear(); m_schema_tree.clear(); m_schema_map.clear(); + m_timestamp_dict.clear(); m_encoded_message_size = 0UL; m_uncompressed_size = 0UL; m_compressed_size = 0UL; + m_next_log_event_id = 0; +} + +size_t ArchiveWriter::write_timestamp_dict() { + std::string timestamp_dict_path = m_archive_path + constants::cArchiveTimestampDictFile; + FileWriter timestamp_dict_file_writer; + ZstdCompressor timestamp_dict_compressor; + timestamp_dict_file_writer.open(timestamp_dict_path, FileWriter::OpenMode::CreateForWriting); + timestamp_dict_compressor.open(timestamp_dict_file_writer, m_compression_level); + std::stringstream timestamp_dict_stream; + m_timestamp_dict.write(timestamp_dict_stream); + std::string encoded_timestamp_dict = timestamp_dict_stream.str(); + timestamp_dict_compressor.write(encoded_timestamp_dict.data(), encoded_timestamp_dict.size()); + timestamp_dict_compressor.close(); + auto compressed_size = timestamp_dict_file_writer.get_pos(); + timestamp_dict_file_writer.close(); + return compressed_size; +} + +void ArchiveWriter::write_single_file_archive(std::vector const& files) { + std::string single_file_archive_path = (std::filesystem::path(m_archives_dir) / m_id).string(); + FileWriter archive_writer; + archive_writer.open(single_file_archive_path, FileWriter::OpenMode::CreateForWriting); + + write_archive_metadata(archive_writer, files); + size_t metadata_section_size = archive_writer.get_pos() - sizeof(ArchiveHeader); + write_archive_files(archive_writer, files); + m_compressed_size = archive_writer.get_pos(); + write_archive_header(archive_writer, metadata_section_size); + + archive_writer.close(); + std::error_code ec; + if (false == std::filesystem::remove(m_archive_path, ec)) { + throw OperationFailed(ErrorCodeFileExists, __FILENAME__, __LINE__); + } +} + +void ArchiveWriter::write_archive_metadata( + FileWriter& archive_writer, + std::vector const& files +) { + archive_writer.seek_from_begin(sizeof(ArchiveHeader)); + + ZstdCompressor compressor; + compressor.open(archive_writer, m_compression_level); + compressor.write_numeric_value(static_cast(3U)); // Number of packets + + // Write archive info + ArchiveInfoPacket archive_info{.num_segments = 1}; + std::stringstream msgpack_buffer; + msgpack::pack(msgpack_buffer, archive_info); + std::string archive_info_str = msgpack_buffer.str(); + compressor.write_numeric_value(ArchiveMetadataPacketType::ArchiveInfo); + compressor.write_numeric_value(static_cast(archive_info_str.size())); + compressor.write_string(archive_info_str); + + // Write archive file info + ArchiveFileInfoPacket archive_file_info{.files{files}}; + msgpack_buffer = std::stringstream{}; + msgpack::pack(msgpack_buffer, archive_file_info); + std::string archive_file_info_str = msgpack_buffer.str(); + compressor.write_numeric_value(ArchiveMetadataPacketType::ArchiveFileInfo); + compressor.write_numeric_value(static_cast(archive_file_info_str.size())); + compressor.write_string(archive_file_info_str); + + // Write timestamp dictionary + compressor.write_numeric_value(ArchiveMetadataPacketType::TimestampDictionary); + std::stringstream timestamp_dict_stream; + m_timestamp_dict.write(timestamp_dict_stream); + std::string encoded_timestamp_dict = timestamp_dict_stream.str(); + compressor.write_numeric_value(static_cast(encoded_timestamp_dict.size())); + compressor.write(encoded_timestamp_dict.data(), encoded_timestamp_dict.size()); + + compressor.close(); +} + +void ArchiveWriter::write_archive_files( + FileWriter& archive_writer, + std::vector const& files +) { + FileReader reader; + for (auto const& file : files) { + std::string file_path = m_archive_path + file.n; + reader.open(file_path); + char read_buffer[cReadBlockSize]; + while (true) { + size_t num_bytes_read{0}; + ErrorCode const error_code + = reader.try_read(read_buffer, cReadBlockSize, num_bytes_read); + if (ErrorCodeEndOfFile == error_code) { + break; + } else if (ErrorCodeSuccess != error_code) { + throw OperationFailed(error_code, __FILENAME__, __LINE__); + } + archive_writer.write(read_buffer, num_bytes_read); + } + reader.close(); + if (false == std::filesystem::remove(file_path)) { + throw OperationFailed(ErrorCodeFileExists, __FILENAME__, __LINE__); + } + } +} + +void ArchiveWriter::write_archive_header(FileWriter& archive_writer, size_t metadata_section_size) { + ArchiveHeader header{ + .magic_number{0}, + .version + = (cArchiveMajorVersion << 24) | (cArchiveMinorVersion << 16) | cArchivePatchVersion, + .uncompressed_size = m_uncompressed_size, + .compressed_size = m_compressed_size, + .reserved_padding{0}, + .metadata_section_size = static_cast(metadata_section_size), + .compression_type = static_cast(ArchiveCompressionType::Zstd), + .padding = 0 + }; + std::memcpy(&header.magic_number, cStructuredSFAMagicNumber, sizeof(header.magic_number)); + archive_writer.seek_from_begin(0); + archive_writer.write(reinterpret_cast(&header), sizeof(header)); } void ArchiveWriter::append_message( @@ -83,6 +234,7 @@ void ArchiveWriter::append_message( } m_encoded_message_size += schema_writer->append_message(message); + ++m_next_log_event_id; } size_t ArchiveWriter::get_data_size() { @@ -127,8 +279,7 @@ void ArchiveWriter::initialize_schema_writer(SchemaWriter* writer, Schema const& } } -size_t ArchiveWriter::store_tables() { - size_t compressed_size = 0; +std::pair ArchiveWriter::store_tables() { m_tables_file_writer.open( m_archive_path + constants::cArchiveTablesFile, FileWriter::OpenMode::CreateForWriting @@ -138,28 +289,113 @@ size_t ArchiveWriter::store_tables() { FileWriter::OpenMode::CreateForWriting ); m_table_metadata_compressor.open(m_table_metadata_file_writer, m_compression_level); - m_table_metadata_compressor.write_numeric_value(m_id_to_schema_writer.size()); - for (auto& i : m_id_to_schema_writer) { - m_table_metadata_compressor.write_numeric_value(i.first); - m_table_metadata_compressor.write_numeric_value(i.second->get_num_messages()); - m_table_metadata_compressor.write_numeric_value(m_tables_file_writer.get_pos()); - - m_tables_compressor.open(m_tables_file_writer, m_compression_level); - size_t uncompressed_size = i.second->store(m_tables_compressor); - m_tables_compressor.close(); - delete i.second; - - m_table_metadata_compressor.write_numeric_value(uncompressed_size); + + /** + * Packed stream metadata schema + * ------------------------------ + * Schema tables are packed into a series of compression streams. Each of those compression + * streams is identified by a 64 bit stream id. In the first half of the metadata we identify + * how many streams there are, and the offset into the file where each compression stream can + * be found. In the second half of the metadata we record how many schema tables there are, + * which compression stream they belong to, the offset into that compression stream where + * they can be found, and how many messages that schema table contains. + * + * Section 1: Compression Streams Metadata + * - Contains metadata about each compression stream. + * - Structure: + * - Number of packed streams: <64-bit integer> + * - For each stream: + * - Offset into the file: <64-bit integer> + * - Uncompressed size: <64-bit integer> + * - Number of separate column schemas: <64-bit integer> + * It is always 0 in the current implementation. + * - Undefined section for separate column schemas, reserved for future support. + * + * Section 2: Schema Tables Metadata + * - Contains metadata about schema tables associated with each compression stream. + * - Structure: + * - Number of schema tables: <64-bit integer> + * - For each schema table: + * - Stream ID: <64-bit integer> + * - Offset into the stream: <64-bit integer> + * - Schema ID: <32-bit integer> + * - Number of messages: <64-bit integer> + * + * We buffer the first half of the metadata in the "stream_metadata" vector, and the second half + * of the metadata in the "schema_metadata" vector as we compress the tables. The metadata is + * flushed once all of the schema tables have been compressed. + */ + using schema_map_it = decltype(m_id_to_schema_writer)::iterator; + std::vector schemas; + std::vector stream_metadata; + std::vector schema_metadata; + + schema_metadata.reserve(m_id_to_schema_writer.size()); + schemas.reserve(m_id_to_schema_writer.size()); + for (auto it = m_id_to_schema_writer.begin(); it != m_id_to_schema_writer.end(); ++it) { + schemas.push_back(it); + } + auto comp = [](schema_map_it const& lhs, schema_map_it const& rhs) -> bool { + return lhs->second->get_total_uncompressed_size() + > rhs->second->get_total_uncompressed_size(); + }; + std::sort(schemas.begin(), schemas.end(), comp); + + uint64_t current_stream_offset = 0; + uint64_t current_stream_id = 0; + uint64_t current_table_file_offset = 0; + m_tables_compressor.open(m_tables_file_writer, m_compression_level); + for (auto it : schemas) { + it->second->store(m_tables_compressor); + schema_metadata.emplace_back( + current_stream_id, + current_stream_offset, + it->first, + it->second->get_num_messages() + ); + current_stream_offset += it->second->get_total_uncompressed_size(); + delete it->second; + + if (current_stream_offset > m_min_table_size || schemas.size() == schema_metadata.size()) { + stream_metadata.emplace_back(current_table_file_offset, current_stream_offset); + m_tables_compressor.close(); + current_stream_offset = 0; + ++current_stream_id; + current_table_file_offset = m_tables_file_writer.get_pos(); + + if (schemas.size() != schema_metadata.size()) { + m_tables_compressor.open(m_tables_file_writer, m_compression_level); + } + } + } + + m_table_metadata_compressor.write_numeric_value(stream_metadata.size()); + for (auto& stream : stream_metadata) { + m_table_metadata_compressor.write_numeric_value(stream.file_offset); + m_table_metadata_compressor.write_numeric_value(stream.uncompressed_size); + } + + // The current implementation doesn't store large tables as separate columns, so this is always + // zero. + size_t const num_separate_column_schemas{0}; + m_table_metadata_compressor.write_numeric_value(num_separate_column_schemas); + + m_table_metadata_compressor.write_numeric_value(schema_metadata.size()); + for (auto& schema : schema_metadata) { + m_table_metadata_compressor.write_numeric_value(schema.stream_id); + m_table_metadata_compressor.write_numeric_value(schema.stream_offset); + m_table_metadata_compressor.write_numeric_value(schema.schema_id); + m_table_metadata_compressor.write_numeric_value(schema.num_messages); } m_table_metadata_compressor.close(); - compressed_size += m_table_metadata_file_writer.get_pos(); - compressed_size += m_tables_file_writer.get_pos(); + auto table_metadata_compressed_size = m_table_metadata_file_writer.get_pos(); + auto table_compressed_size = m_tables_file_writer.get_pos(); m_table_metadata_file_writer.close(); m_tables_file_writer.close(); - return compressed_size; + return {table_metadata_compressed_size, table_compressed_size}; } void ArchiveWriter::update_metadata_db() { @@ -172,8 +408,8 @@ void ArchiveWriter::update_metadata_db() { metadata.increment_static_compressed_size(m_compressed_size); metadata.increment_static_uncompressed_size(m_uncompressed_size); metadata.expand_time_range( - m_timestamp_dict->get_begin_timestamp(), - m_timestamp_dict->get_end_timestamp() + m_timestamp_dict.get_begin_timestamp(), + m_timestamp_dict.get_end_timestamp() ); m_metadata_db->add_archive(m_id, metadata); diff --git a/components/core/src/clp_s/ArchiveWriter.hpp b/components/core/src/clp_s/ArchiveWriter.hpp index 70eb5dc9b..3b13f4426 100644 --- a/components/core/src/clp_s/ArchiveWriter.hpp +++ b/components/core/src/clp_s/ArchiveWriter.hpp @@ -1,6 +1,7 @@ #ifndef CLP_S_ARCHIVEWRITER_HPP #define CLP_S_ARCHIVEWRITER_HPP +#include #include #include @@ -13,6 +14,7 @@ #include "SchemaMap.hpp" #include "SchemaTree.hpp" #include "SchemaWriter.hpp" +#include "SingleFileArchiveDefs.hpp" #include "TimestampDictionaryWriter.hpp" namespace clp_s { @@ -21,6 +23,8 @@ struct ArchiveWriterOption { std::string archives_dir; int compression_level; bool print_archive_stats; + bool single_file_archive; + size_t min_table_size; }; class ArchiveWriter { @@ -32,6 +36,33 @@ class ArchiveWriter { : TraceableException(error_code, filename, line_number) {} }; + struct StreamMetadata { + StreamMetadata(uint64_t file_offset, uint64_t uncompressed_size) + : file_offset(file_offset), + uncompressed_size(uncompressed_size) {} + + uint64_t file_offset{}; + uint64_t uncompressed_size{}; + }; + + struct SchemaMetadata { + SchemaMetadata( + uint64_t stream_id, + uint64_t stream_offset, + int32_t schema_id, + uint64_t num_messages + ) + : stream_id(stream_id), + stream_offset(stream_offset), + schema_id(schema_id), + num_messages(num_messages) {} + + uint64_t stream_id{}; + uint64_t stream_offset{}; + int32_t schema_id{}; + uint64_t num_messages{}; + }; + // Constructor explicit ArchiveWriter(std::shared_ptr metadata_db) : m_metadata_db(std::move(metadata_db)) {} @@ -65,10 +96,15 @@ class ArchiveWriter { * @param key * @return the node id */ - int32_t add_node(int parent_node_id, NodeType type, std::string const& key) { + int32_t add_node(int parent_node_id, NodeType type, std::string_view const key) { return m_schema_tree.add_node(parent_node_id, type, key); } + /** + * @return The Id that will be assigned to the next log event when appended to the archive. + */ + int64_t get_next_log_event_id() const { return m_next_log_event_id; } + /** * Return a schema's Id and add the schema to the * schema map if it does not already exist. @@ -91,7 +127,7 @@ class ArchiveWriter { std::string const& timestamp, uint64_t& pattern_id ) { - return m_timestamp_dict->ingest_entry(key, node_id, timestamp, pattern_id); + return m_timestamp_dict.ingest_entry(key, node_id, timestamp, pattern_id); } /** @@ -101,21 +137,24 @@ class ArchiveWriter { * @param timestamp */ void ingest_timestamp_entry(std::string const& key, int32_t node_id, double timestamp) { - m_timestamp_dict->ingest_entry(key, node_id, timestamp); + m_timestamp_dict.ingest_entry(key, node_id, timestamp); } void ingest_timestamp_entry(std::string const& key, int32_t node_id, int64_t timestamp) { - m_timestamp_dict->ingest_entry(key, node_id, timestamp); + m_timestamp_dict.ingest_entry(key, node_id, timestamp); } /** - * Increments the size of the compressed data written to the archive + * Increments the size of the original (uncompressed) logs ingested into the archive. This size + * tracks the raw input size before any encoding or compression. * @param size */ void increment_uncompressed_size(size_t size) { m_uncompressed_size += size; } /** - * @return Size of the uncompressed data written to the archive + * @return The total size of the encoded (uncompressed) data written to the archive. This + * reflects the size of the data after encoding but before compression. + * TODO: Add the size of schema tree, schema map and timestamp dictionary */ size_t get_data_size(); @@ -128,10 +167,40 @@ class ArchiveWriter { void initialize_schema_writer(SchemaWriter* writer, Schema const& schema); /** - * Stores the tables - * @return Size of the compressed data in bytes + * Compresses and stores the tables. + * @return A pair containing: + * - The size of the compressed table metadata in bytes. + * - The size of the compressed tables in bytes. + */ + [[nodiscard]] std::pair store_tables(); + + /** + * Writes the archive to a single file + * @param files */ - [[nodiscard]] size_t store_tables(); + void write_single_file_archive(std::vector const& files); + + /** + * Writes the metadata section of the single file archive + * @param archive_writer + * @param files + */ + void + write_archive_metadata(FileWriter& archive_writer, std::vector const& files); + + /** + * Writes the file section of the single file archive + * @param archive_writer + * @param files + */ + void write_archive_files(FileWriter& archive_writer, std::vector const& files); + + /** + * Writes the header section of the single file archive + * @param archive_writer + * @param metadata_section_size + */ + void write_archive_header(FileWriter& archive_writer, size_t metadata_section_size); /** * Updates the metadata db with the archive's metadata (id, size, timestamp ranges, etc.) @@ -143,22 +212,37 @@ class ArchiveWriter { */ void print_archive_stats(); + /** + * Write the timestamp dictionary as a dedicated file for multi-file archives. + * + * Note: the timestamp dictionary will be moved into the metadata region of multi-file archives + * in a follow-up PR. + * @return the compressed size of the Timestamp Dictionary in bytes + */ + size_t write_timestamp_dict(); + + static constexpr size_t cReadBlockSize = 4 * 1024; + size_t m_encoded_message_size{}; size_t m_uncompressed_size{}; size_t m_compressed_size{}; + int64_t m_next_log_event_id{}; std::string m_id; + std::string m_archives_dir; std::string m_archive_path; std::string m_encoded_messages_dir; std::shared_ptr m_var_dict; std::shared_ptr m_log_dict; std::shared_ptr m_array_dict; // log type dictionary for arrays - std::shared_ptr m_timestamp_dict; + TimestampDictionaryWriter m_timestamp_dict; std::shared_ptr m_metadata_db; int m_compression_level{}; bool m_print_archive_stats{}; + bool m_single_file_archive{}; + size_t m_min_table_size{}; SchemaMap m_schema_map; SchemaTree m_schema_tree; diff --git a/components/core/src/clp_s/CMakeLists.txt b/components/core/src/clp_s/CMakeLists.txt index c8cf08b22..1656a5d59 100644 --- a/components/core/src/clp_s/CMakeLists.txt +++ b/components/core/src/clp_s/CMakeLists.txt @@ -65,6 +65,8 @@ set( JsonParser.cpp JsonParser.hpp JsonSerializer.hpp + PackedStreamReader.cpp + PackedStreamReader.hpp ParsedMessage.hpp ReaderUtils.cpp ReaderUtils.hpp @@ -145,6 +147,8 @@ set( search/Output.hpp search/OutputHandler.cpp search/OutputHandler.hpp + search/Projection.cpp + search/Projection.hpp search/SchemaMatch.cpp search/SchemaMatch.hpp search/SearchUtils.cpp diff --git a/components/core/src/clp_s/ColumnWriter.cpp b/components/core/src/clp_s/ColumnWriter.cpp index 183f17e57..77fa51f15 100644 --- a/components/core/src/clp_s/ColumnWriter.cpp +++ b/components/core/src/clp_s/ColumnWriter.cpp @@ -1,41 +1,37 @@ #include "ColumnWriter.hpp" namespace clp_s { -void Int64ColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = sizeof(int64_t); +size_t Int64ColumnWriter::add_value(ParsedMessage::variable_t& value) { m_values.push_back(std::get(value)); + return sizeof(int64_t); } -size_t Int64ColumnWriter::store(ZstdCompressor& compressor) { +void Int64ColumnWriter::store(ZstdCompressor& compressor) { size_t size = m_values.size() * sizeof(int64_t); compressor.write(reinterpret_cast(m_values.data()), size); - return size; } -void FloatColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = sizeof(double); +size_t FloatColumnWriter::add_value(ParsedMessage::variable_t& value) { m_values.push_back(std::get(value)); + return sizeof(double); } -size_t FloatColumnWriter::store(ZstdCompressor& compressor) { +void FloatColumnWriter::store(ZstdCompressor& compressor) { size_t size = m_values.size() * sizeof(double); compressor.write(reinterpret_cast(m_values.data()), size); - return size; } -void BooleanColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = sizeof(uint8_t); +size_t BooleanColumnWriter::add_value(ParsedMessage::variable_t& value) { m_values.push_back(std::get(value) ? 1 : 0); + return sizeof(uint8_t); } -size_t BooleanColumnWriter::store(ZstdCompressor& compressor) { +void BooleanColumnWriter::store(ZstdCompressor& compressor) { size_t size = m_values.size() * sizeof(uint8_t); compressor.write(reinterpret_cast(m_values.data()), size); - return size; } -void ClpStringColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = sizeof(int64_t); +size_t ClpStringColumnWriter::add_value(ParsedMessage::variable_t& value) { std::string string_var = std::get(value); uint64_t id; uint64_t offset = m_encoded_vars.size(); @@ -48,45 +44,43 @@ void ClpStringColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& m_log_dict->add_entry(m_logtype_entry, id); auto encoded_id = encode_log_dict_id(id, offset); m_logtypes.push_back(encoded_id); - size += sizeof(int64_t) * (m_encoded_vars.size() - offset); + return sizeof(int64_t) + sizeof(int64_t) * (m_encoded_vars.size() - offset); } -size_t ClpStringColumnWriter::store(ZstdCompressor& compressor) { +void ClpStringColumnWriter::store(ZstdCompressor& compressor) { size_t logtypes_size = m_logtypes.size() * sizeof(int64_t); compressor.write(reinterpret_cast(m_logtypes.data()), logtypes_size); size_t encoded_vars_size = m_encoded_vars.size() * sizeof(int64_t); size_t num_encoded_vars = m_encoded_vars.size(); compressor.write_numeric_value(num_encoded_vars); compressor.write(reinterpret_cast(m_encoded_vars.data()), encoded_vars_size); - return logtypes_size + sizeof(num_encoded_vars) + encoded_vars_size; } -void VariableStringColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = sizeof(int64_t); +size_t VariableStringColumnWriter::add_value(ParsedMessage::variable_t& value) { std::string string_var = std::get(value); uint64_t id; m_var_dict->add_entry(string_var, id); m_variables.push_back(id); + return sizeof(int64_t); } -size_t VariableStringColumnWriter::store(ZstdCompressor& compressor) { +void VariableStringColumnWriter::store(ZstdCompressor& compressor) { size_t size = m_variables.size() * sizeof(int64_t); compressor.write(reinterpret_cast(m_variables.data()), size); - return size; } -void DateStringColumnWriter::add_value(ParsedMessage::variable_t& value, size_t& size) { - size = 2 * sizeof(int64_t); +size_t DateStringColumnWriter::add_value(ParsedMessage::variable_t& value) { auto encoded_timestamp = std::get>(value); m_timestamps.push_back(encoded_timestamp.second); m_timestamp_encodings.push_back(encoded_timestamp.first); + return 2 * sizeof(int64_t); + ; } -size_t DateStringColumnWriter::store(ZstdCompressor& compressor) { +void DateStringColumnWriter::store(ZstdCompressor& compressor) { size_t timestamps_size = m_timestamps.size() * sizeof(int64_t); compressor.write(reinterpret_cast(m_timestamps.data()), timestamps_size); size_t encodings_size = m_timestamp_encodings.size() * sizeof(int64_t); compressor.write(reinterpret_cast(m_timestamp_encodings.data()), encodings_size); - return timestamps_size + encodings_size; } } // namespace clp_s diff --git a/components/core/src/clp_s/ColumnWriter.hpp b/components/core/src/clp_s/ColumnWriter.hpp index ce458381e..7282cf7ea 100644 --- a/components/core/src/clp_s/ColumnWriter.hpp +++ b/components/core/src/clp_s/ColumnWriter.hpp @@ -27,16 +27,24 @@ class BaseColumnWriter { /** * Adds a value to the column * @param value - * @param size + * @return the size of the encoded data appended to this column in bytes */ - virtual void add_value(ParsedMessage::variable_t& value, size_t& size) = 0; + virtual size_t add_value(ParsedMessage::variable_t& value) = 0; /** * Stores the column to a compressed file. * @param compressor - * @return the in-memory uncompressed size of the data written to the compressor */ - virtual size_t store(ZstdCompressor& compressor) = 0; + virtual void store(ZstdCompressor& compressor) = 0; + + /** + * Returns the total size of the header data that will be written to the compressor. This header + * size plus the sum of sizes returned by add_value is equal to the total size of data that will + * be written to the compressor in bytes. + * + * @return the total size of header data that will be written to the compressor in bytes + */ + virtual size_t get_total_header_size() const { return 0; } protected: int32_t m_id; @@ -51,9 +59,9 @@ class Int64ColumnWriter : public BaseColumnWriter { ~Int64ColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; - size_t store(ZstdCompressor& compressor) override; + void store(ZstdCompressor& compressor) override; private: std::vector m_values; @@ -68,9 +76,9 @@ class FloatColumnWriter : public BaseColumnWriter { ~FloatColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; - size_t store(ZstdCompressor& compressor) override; + void store(ZstdCompressor& compressor) override; private: std::vector m_values; @@ -85,9 +93,9 @@ class BooleanColumnWriter : public BaseColumnWriter { ~BooleanColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; - size_t store(ZstdCompressor& compressor) override; + void store(ZstdCompressor& compressor) override; private: std::vector m_values; @@ -109,9 +117,11 @@ class ClpStringColumnWriter : public BaseColumnWriter { ~ClpStringColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; + + void store(ZstdCompressor& compressor) override; - size_t store(ZstdCompressor& compressor) override; + size_t get_total_header_size() const override { return sizeof(size_t); } /** * @param encoded_id @@ -163,9 +173,9 @@ class VariableStringColumnWriter : public BaseColumnWriter { ~VariableStringColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; - size_t store(ZstdCompressor& compressor) override; + void store(ZstdCompressor& compressor) override; private: std::shared_ptr m_var_dict; @@ -181,9 +191,9 @@ class DateStringColumnWriter : public BaseColumnWriter { ~DateStringColumnWriter() override = default; // Methods inherited from BaseColumnWriter - void add_value(ParsedMessage::variable_t& value, size_t& size) override; + size_t add_value(ParsedMessage::variable_t& value) override; - size_t store(ZstdCompressor& compressor) override; + void store(ZstdCompressor& compressor) override; private: std::vector m_timestamps; diff --git a/components/core/src/clp_s/CommandLineArguments.cpp b/components/core/src/clp_s/CommandLineArguments.cpp index 640a45f8c..99539b627 100644 --- a/components/core/src/clp_s/CommandLineArguments.cpp +++ b/components/core/src/clp_s/CommandLineArguments.cpp @@ -160,6 +160,11 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { default_value(m_target_encoded_size), "Target size (B) for the dictionaries and encoded messages before a new " "archive is created." + )( + "min-table-size", + po::value(&m_minimum_table_size)->value_name("MIN_TABLE_SIZE")-> + default_value(m_minimum_table_size), + "Minimum size (B) for a packed table before it gets compressed." )( "max-document-size", po::value(&m_max_document_size)->value_name("DOC_SIZE")-> @@ -185,10 +190,18 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { "print-archive-stats", po::bool_switch(&m_print_archive_stats), "Print statistics (json) about the archive after it's compressed." + )( + "single-file-archive", + po::bool_switch(&m_single_file_archive), + "Create a single archive file instead of multiple files." )( "structurize-arrays", po::bool_switch(&m_structurize_arrays), "Structurize arrays instead of compressing them as clp strings." + )( + "disable-log-order", + po::bool_switch(&m_disable_log_order), + "Do not record log order at ingestion time." ); // clang-format on @@ -286,6 +299,37 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { ); extraction_options.add(input_options); + po::options_description decompression_options("Decompression Options"); + // clang-format off + decompression_options.add_options()( + "ordered", + po::bool_switch(&m_ordered_decompression), + "Enable decompression in log order for this archive" + )( + "target-ordered-chunk-size", + po::value(&m_target_ordered_chunk_size) + ->default_value(m_target_ordered_chunk_size) + ->value_name("SIZE"), + "Chunk size (B) for each output file when decompressing records in log order." + " When set to 0, no chunking is performed." + ); + // clang-format on + extraction_options.add(decompression_options); + + po::options_description output_metadata_options("Output Metadata Options"); + // clang-format off + output_metadata_options.add_options()( + "mongodb-uri", + po::value(&m_mongodb_uri)->value_name("URI"), + "MongoDB URI for the database to write decompression metadata to" + )( + "mongodb-collection", + po::value(&m_mongodb_collection)->value_name("COLLECTION"), + "MongoDB collection to write decompression metadata to" + ); + // clang-format on + extraction_options.add(output_metadata_options); + po::positional_options_description positional_options; positional_options.add("archives-dir", 1); positional_options.add("output-dir", 1); @@ -316,6 +360,8 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { po::options_description visible_options; visible_options.add(general_options); visible_options.add(input_options); + visible_options.add(decompression_options); + visible_options.add(output_metadata_options); std::cerr << visible_options << std::endl; return ParsingResult::InfoCommand; } @@ -327,6 +373,26 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { if (m_output_dir.empty()) { throw std::invalid_argument("No output directory specified"); } + + if (0 != m_target_ordered_chunk_size && false == m_ordered_decompression) { + throw std::invalid_argument( + "target-ordered-chunk-size must be used with ordered argument" + ); + } + + // We use xor to check that these arguments are either both specified or both + // unspecified. + if (m_mongodb_uri.empty() ^ m_mongodb_collection.empty()) { + throw std::invalid_argument( + "mongodb-uri and mongodb-collection must both be non-empty" + ); + } + + if (false == m_mongodb_uri.empty() && false == m_ordered_decompression) { + throw std::invalid_argument( + "Recording decompression metadata only supported for ordered decompression" + ); + } } else if ((char)Command::Search == command_input) { std::string archives_dir; std::string query; @@ -374,6 +440,14 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { "archive-id", po::value(&m_archive_id)->value_name("ID"), "Limit search to the archive with the given ID" + )( + "projection", + po::value>(&m_projection_columns) + ->multitoken() + ->value_name("COLUMN_A COLUMN_B ..."), + "Project only the given set of columns for matching results. This option must be" + " specified after all positional options. Values that are objects or structured" + " arrays are currently unsupported." ); // clang-format on search_options.add(match_options); diff --git a/components/core/src/clp_s/CommandLineArguments.hpp b/components/core/src/clp_s/CommandLineArguments.hpp index 20fa3ec6e..a87e9b6bd 100644 --- a/components/core/src/clp_s/CommandLineArguments.hpp +++ b/components/core/src/clp_s/CommandLineArguments.hpp @@ -102,8 +102,20 @@ class CommandLineArguments { OutputHandlerType get_output_handler_type() const { return m_output_handler_type; } + bool get_single_file_archive() const { return m_single_file_archive; } + bool get_structurize_arrays() const { return m_structurize_arrays; } + bool get_ordered_decompression() const { return m_ordered_decompression; } + + size_t get_target_ordered_chunk_size() const { return m_target_ordered_chunk_size; } + + size_t get_minimum_table_size() const { return m_minimum_table_size; } + + std::vector const& get_projection_columns() const { return m_projection_columns; } + + bool get_record_log_order() const { return false == m_disable_log_order; } + private: // Methods /** @@ -166,7 +178,12 @@ class CommandLineArguments { size_t m_target_encoded_size{8ULL * 1024 * 1024 * 1024}; // 8 GiB bool m_print_archive_stats{false}; size_t m_max_document_size{512ULL * 1024 * 1024}; // 512 MB + bool m_single_file_archive{false}; bool m_structurize_arrays{false}; + bool m_ordered_decompression{false}; + size_t m_target_ordered_chunk_size{}; + size_t m_minimum_table_size{1ULL * 1024 * 1024}; // 1 MB + bool m_disable_log_order{false}; // Metadata db variables std::optional m_metadata_db_config; @@ -186,6 +203,7 @@ class CommandLineArguments { std::optional m_search_begin_ts; std::optional m_search_end_ts; bool m_ignore_case{false}; + std::vector m_projection_columns; // Decompression and search variables std::string m_archive_id; diff --git a/components/core/src/clp_s/Defs.hpp b/components/core/src/clp_s/Defs.hpp index 2615098b7..ce2200a05 100644 --- a/components/core/src/clp_s/Defs.hpp +++ b/components/core/src/clp_s/Defs.hpp @@ -11,7 +11,7 @@ namespace clp_s { // Types -typedef int64_t epochtime_t; +using epochtime_t = int64_t; static epochtime_t const cEpochTimeMin = INT64_MIN; static epochtime_t const cEpochTimeMax = INT64_MAX; static double const cDoubleEpochTimeMin = std::numeric_limits::lowest(); @@ -20,21 +20,21 @@ static double const cDoubleEpochTimeMax = std::numeric_limits::max(); #define MICROSECONDS_TO_EPOCHTIME(x) 0 #define EPOCHTIME_T_PRINTF_FMT PRId64 -typedef uint64_t variable_dictionary_id_t; +using variable_dictionary_id_t = uint64_t; static variable_dictionary_id_t const cVariableDictionaryIdMax = UINT64_MAX; -typedef int64_t logtype_dictionary_id_t; +using logtype_dictionary_id_t = int64_t; static logtype_dictionary_id_t const cLogtypeDictionaryIdMax = INT64_MAX; -typedef uint16_t archive_format_version_t; +using archive_format_version_t = uint16_t; // This flag is used to maintain two separate streams of archive format versions: // - Development versions (which can change frequently as necessary) which should have the flag // - Production versions (which should be changed with care and as infrequently as possible) // which should not have the flag constexpr archive_format_version_t cArchiveFormatDevelopmentVersionFlag = 0x8000; -typedef uint64_t file_id_t; -typedef uint64_t segment_id_t; -typedef int64_t encoded_variable_t; +using file_id_t = uint64_t; +using segment_id_t = uint64_t; +using encoded_variable_t = int64_t; } // namespace clp_s // Macros diff --git a/components/core/src/clp_s/DictionaryWriter.hpp b/components/core/src/clp_s/DictionaryWriter.hpp index 9aaebdb4a..0ae136036 100644 --- a/components/core/src/clp_s/DictionaryWriter.hpp +++ b/components/core/src/clp_s/DictionaryWriter.hpp @@ -50,7 +50,7 @@ class DictionaryWriter { protected: // Types - typedef std::unordered_map value_to_id_t; + using value_to_id_t = std::unordered_map; // Variables bool m_is_open; diff --git a/components/core/src/clp_s/JsonConstructor.cpp b/components/core/src/clp_s/JsonConstructor.cpp index 777f62ce2..95e3fa2c5 100644 --- a/components/core/src/clp_s/JsonConstructor.cpp +++ b/components/core/src/clp_s/JsonConstructor.cpp @@ -1,19 +1,23 @@ #include "JsonConstructor.hpp" #include +#include #include #include +#include +#include +#include +#include +#include "archive_constants.hpp" #include "ErrorCode.hpp" #include "ReaderUtils.hpp" #include "SchemaTree.hpp" #include "TraceableException.hpp" namespace clp_s { -JsonConstructor::JsonConstructor(JsonConstructorOption const& option) - : m_output_dir(option.output_dir), - m_archives_dir(option.archives_dir) { +JsonConstructor::JsonConstructor(JsonConstructorOption const& option) : m_option{option} { std::error_code error_code; if (false == std::filesystem::create_directory(option.output_dir, error_code) && error_code) { throw OperationFailed( @@ -28,27 +32,158 @@ JsonConstructor::JsonConstructor(JsonConstructorOption const& option) ); } - if (false == std::filesystem::is_directory(m_archives_dir)) { + std::filesystem::path archive_path{m_option.archives_dir}; + archive_path /= m_option.archive_id; + if (false == std::filesystem::is_directory(archive_path)) { throw OperationFailed( ErrorCodeFailure, __FILENAME__, __LINE__, - fmt::format("'{}' is not a directory", m_archives_dir) + fmt::format("'{}' is not a directory", archive_path.c_str()) ); } } void JsonConstructor::store() { - FileWriter writer; - writer.open(m_output_dir + "/original", FileWriter::OpenMode::CreateIfNonexistentForAppending); - m_archive_reader = std::make_unique(); - m_archive_reader->open(m_archives_dir); + m_archive_reader->open(m_option.archives_dir, m_option.archive_id); m_archive_reader->read_dictionaries_and_metadata(); - m_archive_reader->store(writer); - m_archive_reader->close(); - writer.close(); + if (m_option.ordered && false == m_archive_reader->has_log_order()) { + SPDLOG_WARN("This archive is missing ordering information and can not be decompressed in " + "log order. Falling back to out of order decompression."); + } + + if (false == m_option.ordered || false == m_archive_reader->has_log_order()) { + FileWriter writer; + writer.open( + m_option.output_dir + "/original", + FileWriter::OpenMode::CreateIfNonexistentForAppending + ); + m_archive_reader->store(writer); + + writer.close(); + } else { + construct_in_order(); + } + m_archive_reader->close(); } +void JsonConstructor::construct_in_order() { + std::string buffer; + auto tables = m_archive_reader->read_all_tables(); + using ReaderPointer = std::shared_ptr; + auto cmp = [](ReaderPointer& left, ReaderPointer& right) { + return left->get_next_log_event_idx() > right->get_next_log_event_idx(); + }; + std::priority_queue record_queue(tables.begin(), tables.end(), cmp); + // Clear tables vector so that memory gets deallocated after we have marshalled all records for + // a given table + tables.clear(); + + int64_t first_idx{}; + int64_t last_idx{}; + size_t chunk_size{}; + auto src_path = std::filesystem::path(m_option.output_dir) / m_option.archive_id; + FileWriter writer; + writer.open(src_path, FileWriter::OpenMode::CreateForWriting); + + mongocxx::client client; + mongocxx::collection collection; + + if (m_option.metadata_db.has_value()) { + try { + auto const mongo_uri{mongocxx::uri(m_option.metadata_db->mongodb_uri)}; + client = mongocxx::client{mongo_uri}; + collection = client[mongo_uri.database()][m_option.metadata_db->mongodb_collection]; + } catch (mongocxx::exception const& e) { + throw OperationFailed(ErrorCodeBadParamDbUri, __FILE__, __LINE__, e.what()); + } + } + + std::vector results; + auto finalize_chunk = [&](bool open_new_writer) { + // Add one to last_idx to match clp's behaviour of having the end index be exclusive + ++last_idx; + writer.close(); + std::string new_file_name = src_path.string() + "_" + std::to_string(first_idx) + "_" + + std::to_string(last_idx) + ".jsonl"; + auto new_file_path = std::filesystem::path(new_file_name); + std::error_code ec; + std::filesystem::rename(src_path, new_file_path, ec); + if (ec) { + throw OperationFailed(ErrorCodeFailure, __FILE__, __LINE__, ec.message()); + } + + if (m_option.metadata_db.has_value()) { + results.emplace_back(std::move(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cPath, + new_file_path.filename() + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cOrigFileId, + m_option.archive_id + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cBeginMsgIx, + first_idx + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cEndMsgIx, + last_idx + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cIsLastIrChunk, + false == open_new_writer + ) + ))); + } + + if (open_new_writer) { + writer.open(src_path, FileWriter::OpenMode::CreateForWriting); + } + }; + + while (false == record_queue.empty()) { + ReaderPointer next = record_queue.top(); + record_queue.pop(); + last_idx = next->get_next_log_event_idx(); + if (0 == chunk_size) { + first_idx = last_idx; + } + next->get_next_message(buffer); + if (false == next->done()) { + record_queue.emplace(std::move(next)); + } + writer.write(buffer.c_str(), buffer.length()); + chunk_size += buffer.length(); + + if (0 != m_option.target_ordered_chunk_size + && chunk_size >= m_option.target_ordered_chunk_size) + { + finalize_chunk(true); + chunk_size = 0; + } + } + + if (chunk_size > 0) { + finalize_chunk(false); + } else { + writer.close(); + std::error_code ec; + std::filesystem::remove(src_path, ec); + if (ec) { + throw OperationFailed(ErrorCodeFailure, __FILE__, __LINE__, ec.message()); + } + } + + if (false == results.empty()) { + try { + collection.insert_many(results); + } catch (mongocxx::exception const& e) { + throw OperationFailed(ErrorCodeFailureDbBulkWrite, __FILE__, __LINE__, e.what()); + } + } +} } // namespace clp_s diff --git a/components/core/src/clp_s/JsonConstructor.hpp b/components/core/src/clp_s/JsonConstructor.hpp index 8aa35f904..3d9228a02 100644 --- a/components/core/src/clp_s/JsonConstructor.hpp +++ b/components/core/src/clp_s/JsonConstructor.hpp @@ -1,6 +1,7 @@ #ifndef CLP_S_JSONCONSTRUCTOR_HPP #define CLP_S_JSONCONSTRUCTOR_HPP +#include #include #include #include @@ -9,14 +10,28 @@ #include "ColumnReader.hpp" #include "DictionaryReader.hpp" #include "ErrorCode.hpp" +#include "FileWriter.hpp" #include "SchemaReader.hpp" #include "SchemaTree.hpp" #include "TraceableException.hpp" namespace clp_s { +struct MetadataDbOption { + MetadataDbOption(std::string const& uri, std::string const& collection) + : mongodb_uri{uri}, + mongodb_collection{collection} {} + + std::string mongodb_uri; + std::string mongodb_collection; +}; + struct JsonConstructorOption { std::string archives_dir; + std::string archive_id; std::string output_dir; + bool ordered{false}; + size_t target_ordered_chunk_size{}; + std::optional metadata_db; }; class JsonConstructor { @@ -49,9 +64,13 @@ class JsonConstructor { void store(); private: - std::string m_archives_dir; - std::string m_output_dir; + /** + * Reads all of the tables from m_archive_reader and writes all of the records + * they contain to writer in log order. + */ + void construct_in_order(); + JsonConstructorOption m_option{}; std::unique_ptr m_archive_reader; }; } // namespace clp_s diff --git a/components/core/src/clp_s/JsonFileIterator.cpp b/components/core/src/clp_s/JsonFileIterator.cpp index 5fffcc8f9..ad6d16cd0 100644 --- a/components/core/src/clp_s/JsonFileIterator.cpp +++ b/components/core/src/clp_s/JsonFileIterator.cpp @@ -156,4 +156,14 @@ bool JsonFileIterator::get_json(simdjson::ondemand::document_stream::iterator& i } while (read_new_json()); return false; } + +size_t JsonFileIterator::get_num_bytes_consumed() { + // If there are more documents left in the current buffer account for how much of the + // buffer has been consumed, otherwise report the total number of bytes read so that we + // capture trailing whitespace. + if (m_doc_it != m_stream.end()) { + return m_bytes_read - (m_buf_occupied - m_next_document_position); + } + return m_bytes_read; +} } // namespace clp_s diff --git a/components/core/src/clp_s/JsonFileIterator.hpp b/components/core/src/clp_s/JsonFileIterator.hpp index 51422963a..b8db3f4f2 100644 --- a/components/core/src/clp_s/JsonFileIterator.hpp +++ b/components/core/src/clp_s/JsonFileIterator.hpp @@ -51,6 +51,14 @@ class JsonFileIterator { */ [[nodiscard]] size_t get_num_bytes_read() const { return m_bytes_read; } + /** + * Note: this method can not be const because checking if a simdjson iterator is at the end + * of a document stream is non-const. + * + * @return total number of bytes consumed from the file via get_json + */ + [[nodiscard]] size_t get_num_bytes_consumed(); + /** * @return the last error code encountered when iterating over the json file */ diff --git a/components/core/src/clp_s/JsonParser.cpp b/components/core/src/clp_s/JsonParser.cpp index c4fe7a43e..d14a221b3 100644 --- a/components/core/src/clp_s/JsonParser.cpp +++ b/components/core/src/clp_s/JsonParser.cpp @@ -11,33 +11,38 @@ namespace clp_s { JsonParser::JsonParser(JsonParserOption const& option) - : m_archives_dir(option.archives_dir), - m_num_messages(0), - m_compression_level(option.compression_level), + : m_num_messages(0), m_target_encoded_size(option.target_encoded_size), m_max_document_size(option.max_document_size), m_timestamp_key(option.timestamp_key), - m_structurize_arrays(option.structurize_arrays) { + m_structurize_arrays(option.structurize_arrays), + m_record_log_order(option.record_log_order) { if (false == FileUtils::validate_path(option.file_paths)) { exit(1); } if (false == m_timestamp_key.empty()) { - clp_s::StringUtils::tokenize_column_descriptor(m_timestamp_key, m_timestamp_column); + if (false + == clp_s::StringUtils::tokenize_column_descriptor(m_timestamp_key, m_timestamp_column)) + { + SPDLOG_ERROR("Can not parse invalid timestamp key: \"{}\"", m_timestamp_key); + throw OperationFailed(ErrorCodeBadParam, __FILENAME__, __LINE__); + } } for (auto& file_path : option.file_paths) { FileUtils::find_all_files(file_path, m_file_paths); } - ArchiveWriterOption archive_writer_option; - archive_writer_option.archives_dir = m_archives_dir; - archive_writer_option.id = m_generator(); - archive_writer_option.compression_level = option.compression_level; - archive_writer_option.print_archive_stats = option.print_archive_stats; + m_archive_options.archives_dir = option.archives_dir; + m_archive_options.compression_level = option.compression_level; + m_archive_options.print_archive_stats = option.print_archive_stats; + m_archive_options.single_file_archive = option.single_file_archive; + m_archive_options.min_table_size = option.min_table_size; + m_archive_options.id = m_generator(); m_archive_writer = std::make_unique(option.metadata_db); - m_archive_writer->open(archive_writer_option); + m_archive_writer->open(m_archive_options); } void JsonParser::parse_obj_in_array(ondemand::object line, int32_t parent_node_id) { @@ -431,7 +436,7 @@ bool JsonParser::parse() { if (simdjson::error_code::SUCCESS != json_file_iterator.get_error()) { SPDLOG_ERROR( - "Encountered error - {} - while trying to parse {}", + "Encountered error - {} - while trying to parse {} after parsing 0 bytes", simdjson::error_message(json_file_iterator.get_error()), file_path ); @@ -442,7 +447,18 @@ bool JsonParser::parse() { simdjson::ondemand::document_stream::iterator json_it; m_num_messages = 0; - size_t last_num_bytes_read = 0; + size_t bytes_consumed_up_to_prev_archive = 0; + size_t bytes_consumed_up_to_prev_record = 0; + + int32_t log_event_idx_node_id{}; + auto add_log_event_idx_node = [&]() { + if (m_record_log_order) { + log_event_idx_node_id + = add_metadata_field(constants::cLogEventIdxName, NodeType::Integer); + } + }; + add_log_event_idx_node(); + while (json_file_iterator.get_json(json_it)) { m_current_schema.clear(); @@ -453,11 +469,40 @@ bool JsonParser::parse() { // that this isn't a valid JSON document but they get set in different situations so we // need to check both here. if (is_scalar_result.error() || true == is_scalar_result.value()) { - SPDLOG_ERROR("Encountered non-json-object while trying to parse {}", file_path); + SPDLOG_ERROR( + "Encountered non-json-object while trying to parse {} after parsing {} " + "bytes", + file_path, + bytes_consumed_up_to_prev_record + ); + m_archive_writer->close(); + return false; + } + + // Add log_event_idx field to metadata for record + if (m_record_log_order) { + m_current_parsed_message.add_value( + log_event_idx_node_id, + m_archive_writer->get_next_log_event_id() + ); + m_current_schema.insert_ordered(log_event_idx_node_id); + } + + // Some errors from simdjson are latent until trying to access invalid JSON fields. + // Instead of checking for an error every time we access a JSON field in parse_line we + // just catch simdjson_error here instead. + try { + parse_line(ref.value(), constants::cRootNodeId, constants::cRootNodeName); + } catch (simdjson::simdjson_error& error) { + SPDLOG_ERROR( + "Encountered error - {} - while trying to parse {} after parsing {} bytes", + error.what(), + file_path, + bytes_consumed_up_to_prev_record + ); m_archive_writer->close(); return false; } - parse_line(ref.value(), -1, ""); m_num_messages++; int32_t current_schema_id = m_archive_writer->add_schema(m_current_schema); @@ -465,25 +510,29 @@ bool JsonParser::parse() { m_archive_writer ->append_message(current_schema_id, m_current_schema, m_current_parsed_message); + bytes_consumed_up_to_prev_record = json_file_iterator.get_num_bytes_consumed(); if (m_archive_writer->get_data_size() >= m_target_encoded_size) { - size_t num_bytes_read = json_file_iterator.get_num_bytes_read(); - m_archive_writer->increment_uncompressed_size(num_bytes_read - last_num_bytes_read); - last_num_bytes_read = num_bytes_read; + m_archive_writer->increment_uncompressed_size( + bytes_consumed_up_to_prev_record - bytes_consumed_up_to_prev_archive + ); + bytes_consumed_up_to_prev_archive = bytes_consumed_up_to_prev_record; split_archive(); + add_log_event_idx_node(); } m_current_parsed_message.clear(); } m_archive_writer->increment_uncompressed_size( - json_file_iterator.get_num_bytes_read() - last_num_bytes_read + json_file_iterator.get_num_bytes_read() - bytes_consumed_up_to_prev_archive ); if (simdjson::error_code::SUCCESS != json_file_iterator.get_error()) { SPDLOG_ERROR( - "Encountered error - {} - while trying to parse {}", + "Encountered error - {} - while trying to parse {} after parsing {} bytes", simdjson::error_message(json_file_iterator.get_error()), - file_path + file_path, + bytes_consumed_up_to_prev_record ); m_archive_writer->close(); return false; @@ -499,19 +548,23 @@ bool JsonParser::parse() { return true; } +int32_t JsonParser::add_metadata_field(std::string_view const field_name, NodeType type) { + auto metadata_subtree_id = m_archive_writer->add_node( + constants::cRootNodeId, + NodeType::Metadata, + constants::cMetadataSubtreeName + ); + return m_archive_writer->add_node(metadata_subtree_id, type, field_name); +} + void JsonParser::store() { m_archive_writer->close(); } void JsonParser::split_archive() { m_archive_writer->close(); - - ArchiveWriterOption archive_writer_option; - archive_writer_option.archives_dir = m_archives_dir; - archive_writer_option.id = m_generator(); - archive_writer_option.compression_level = m_compression_level; - - m_archive_writer->open(archive_writer_option); + m_archive_options.id = m_generator(); + m_archive_writer->open(m_archive_options); } } // namespace clp_s diff --git a/components/core/src/clp_s/JsonParser.hpp b/components/core/src/clp_s/JsonParser.hpp index a0010e66f..bfd423c22 100644 --- a/components/core/src/clp_s/JsonParser.hpp +++ b/components/core/src/clp_s/JsonParser.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -30,11 +31,14 @@ struct JsonParserOption { std::vector file_paths; std::string timestamp_key; std::string archives_dir; - size_t target_encoded_size; - size_t max_document_size; - int compression_level; - bool print_archive_stats; - bool structurize_arrays; + size_t target_encoded_size{}; + size_t max_document_size{}; + size_t min_table_size{}; + int compression_level{}; + bool print_archive_stats{}; + bool structurize_arrays{}; + bool record_log_order{true}; + bool single_file_archive{false}; std::shared_ptr metadata_db; }; @@ -70,6 +74,7 @@ class JsonParser { * @param line the JSON line * @param parent_node_id the parent node id * @param key the key of the node + * @throw simdjson::simdjson_error when encountering invalid fields while parsing line */ void parse_line(ondemand::value line, int32_t parent_node_id, std::string const& key); @@ -92,11 +97,16 @@ class JsonParser { */ void split_archive(); + /** + * Adds an internal field to the MPT and get its Id. + * + * Note: this method should be called before parsing a record so that internal fields come first + * in each table. This isn't strictly necessary, but it is a nice convention. + */ + int32_t add_metadata_field(std::string_view const field_name, NodeType type); + int m_num_messages; - int m_compression_level; std::vector m_file_paths; - std::string m_archives_dir; - std::string m_schema_tree_path; Schema m_current_schema; ParsedMessage m_current_parsed_message; @@ -106,9 +116,11 @@ class JsonParser { boost::uuids::random_generator m_generator; std::unique_ptr m_archive_writer; + ArchiveWriterOption m_archive_options{}; size_t m_target_encoded_size; size_t m_max_document_size; bool m_structurize_arrays{false}; + bool m_record_log_order{true}; }; } // namespace clp_s diff --git a/components/core/src/clp_s/JsonSerializer.hpp b/components/core/src/clp_s/JsonSerializer.hpp index 01a8a1e74..63f845525 100644 --- a/components/core/src/clp_s/JsonSerializer.hpp +++ b/components/core/src/clp_s/JsonSerializer.hpp @@ -2,6 +2,7 @@ #define CLP_S_JSONSERIALIZER_HPP #include +#include #include #include "ColumnReader.hpp" @@ -66,7 +67,7 @@ class JsonSerializer { return false; } - void add_special_key(std::string const& key) { m_special_keys.push_back(key); } + void add_special_key(std::string_view const key) { m_special_keys.emplace_back(key); } void begin_object() { append_key(); @@ -75,7 +76,13 @@ class JsonSerializer { void begin_document() { m_json_string += "{"; } - void end_document() { m_json_string[m_json_string.size() - 1] = '}'; } + void end_document() { + if ('{' != m_json_string.back()) { + m_json_string[m_json_string.size() - 1] = '}'; + } else { + m_json_string += '}'; + } + } void end_object() { if (m_op_list[m_op_list_index - 2] != BeginObject @@ -104,13 +111,13 @@ class JsonSerializer { void append_key() { append_key(m_special_keys[m_special_keys_index++]); } - void append_key(std::string const& key) { + void append_key(std::string_view const key) { m_json_string += "\""; m_json_string += key; m_json_string += "\":"; } - void append_value(std::string const& value) { + void append_value(std::string_view const value) { m_json_string += value; m_json_string += ","; } diff --git a/components/core/src/clp_s/PackedStreamReader.cpp b/components/core/src/clp_s/PackedStreamReader.cpp new file mode 100644 index 000000000..44eb94e96 --- /dev/null +++ b/components/core/src/clp_s/PackedStreamReader.cpp @@ -0,0 +1,117 @@ +#include "PackedStreamReader.hpp" + +namespace clp_s { + +void PackedStreamReader::read_metadata(ZstdDecompressor& decompressor) { + switch (m_state) { + case PackedStreamReaderState::Uninitialized: + m_state = PackedStreamReaderState::MetadataRead; + break; + case PackedStreamReaderState::PackedStreamsOpened: + m_state = PackedStreamReaderState::PackedStreamsOpenedAndMetadataRead; + break; + default: + throw OperationFailed(ErrorCodeNotReady, __FILE__, __LINE__); + } + + size_t num_streams; + if (auto error = decompressor.try_read_numeric_value(num_streams); ErrorCodeSuccess != error) { + throw OperationFailed(error, __FILE__, __LINE__); + } + m_stream_metadata.reserve(num_streams); + + for (size_t i = 0; i < num_streams; ++i) { + size_t file_offset; + size_t uncompressed_size; + + if (auto error = decompressor.try_read_numeric_value(file_offset); + ErrorCodeSuccess != error) + { + throw OperationFailed(error, __FILE__, __LINE__); + } + + if (auto error = decompressor.try_read_numeric_value(uncompressed_size); + ErrorCodeSuccess != error) + { + throw OperationFailed(error, __FILE__, __LINE__); + } + + m_stream_metadata.emplace_back(file_offset, uncompressed_size); + } +} + +void PackedStreamReader::open_packed_streams(std::string const& tables_file_path) { + switch (m_state) { + case PackedStreamReaderState::Uninitialized: + m_state = PackedStreamReaderState::PackedStreamsOpened; + break; + case PackedStreamReaderState::MetadataRead: + m_state = PackedStreamReaderState::PackedStreamsOpenedAndMetadataRead; + break; + default: + throw OperationFailed(ErrorCodeNotReady, __FILE__, __LINE__); + } + m_packed_stream_reader.open(tables_file_path); +} + +void PackedStreamReader::close() { + switch (m_state) { + case PackedStreamReaderState::PackedStreamsOpened: + case PackedStreamReaderState::PackedStreamsOpenedAndMetadataRead: + case PackedStreamReaderState::ReadingPackedStreams: + break; + default: + throw OperationFailed(ErrorCodeNotReady, __FILE__, __LINE__); + } + m_packed_stream_reader.close(); + m_prev_stream_id = 0; + m_stream_metadata.clear(); + m_state = PackedStreamReaderState::Uninitialized; +} + +void PackedStreamReader::read_stream( + size_t stream_id, + std::shared_ptr& buf, + size_t& buf_size +) { + constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB + if (stream_id >= m_stream_metadata.size()) { + throw OperationFailed(ErrorCodeCorrupt, __FILE__, __LINE__); + } + + switch (m_state) { + case PackedStreamReaderState::PackedStreamsOpenedAndMetadataRead: + m_state = PackedStreamReaderState::ReadingPackedStreams; + break; + case PackedStreamReaderState::ReadingPackedStreams: + if (m_prev_stream_id >= stream_id) { + throw OperationFailed(ErrorCodeBadParam, __FILE__, __LINE__); + } + break; + default: + throw OperationFailed(ErrorCodeNotReady, __FILE__, __LINE__); + } + m_prev_stream_id = stream_id; + + auto& [file_offset, uncompressed_size] = m_stream_metadata[stream_id]; + if (auto error = m_packed_stream_reader.try_seek_from_begin(file_offset); + ErrorCodeSuccess != error) + { + throw OperationFailed(error, __FILE__, __LINE__); + } + m_packed_stream_decompressor.open(m_packed_stream_reader, cDecompressorFileReadBufferCapacity); + if (buf_size < uncompressed_size) { + // make_shared is supposed to work here for c++20, but it seems like the compiler version + // we use doesn't support it, so we convert a unique_ptr to a shared_ptr instead. + buf = std::make_unique(uncompressed_size); + buf_size = uncompressed_size; + } + if (auto error + = m_packed_stream_decompressor.try_read_exact_length(buf.get(), uncompressed_size); + ErrorCodeSuccess != error) + { + throw OperationFailed(error, __FILE__, __LINE__); + } + m_packed_stream_decompressor.close_for_reuse(); +} +} // namespace clp_s diff --git a/components/core/src/clp_s/PackedStreamReader.hpp b/components/core/src/clp_s/PackedStreamReader.hpp new file mode 100644 index 000000000..d9f9af58f --- /dev/null +++ b/components/core/src/clp_s/PackedStreamReader.hpp @@ -0,0 +1,97 @@ +#ifndef CLP_S_PACKEDSTREAMREADER_HPP +#define CLP_S_PACKEDSTREAMREADER_HPP + +#include +#include +#include +#include + +#include "FileReader.hpp" +#include "ZstdDecompressor.hpp" + +namespace clp_s { +/** + * PackedStreamReader ensures that the tables section of an archive is read safely. Any attempt to + * read the tables section without loading the tables metadata, and any attempt to read tables + * section out of order will throw. As well, any incorrect usage of this class (e.g. closing without + * opening) will throw. + */ +class PackedStreamReader { +public: + class OperationFailed : public TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : TraceableException(error_code, filename, line_number) {} + }; + + struct PackedStreamMetadata { + PackedStreamMetadata(size_t offset, size_t size) + : file_offset(offset), + uncompressed_size(size) {} + + size_t file_offset; + size_t uncompressed_size; + }; + + /** + * Reads packed stream metadata from the provided compression stream. Must be invoked before + * reading packed streams. + * @param decompressor an open ZstdDecompressor pointing to the packed stream metadata + */ + void read_metadata(ZstdDecompressor& decompressor); + + /** + * Opens a file reader for the tables section. Must be invoked before reading packed streams. + * @param tables_file_path the path to the tables file for the archive being read + */ + void open_packed_streams(std::string const& tables_file_path); + + /** + * Closes the file reader for the tables section. + */ + void close(); + + /** + * Decompresses a stream with a given stream_id and returns it. This function must be called + * strictly in ascending stream_id order. If this function is called twice for the same stream + * or if a stream with lower id is requested after a stream with higher id then an error is + * thrown. + * + * Note: the buffer and buffer size are returned by reference. This is to support the use case + * where the caller wants to re-use the same buffer for multiple streams to avoid allocations + * when they already have a sufficiently large buffer. If no buffer is provided or the provided + * buffer is too small calling read_stream will create a buffer exactly as large as the stream + * being decompressed. + * + * @param stream_id + * @param buf a shared ptr to the buffer where the stream will be read. The buffer gets resized + * if it is too small to contain the requested stream. + * @param buf_size the size of the underlying buffer owned by buf -- passed and updated by + * reference + */ + void read_stream(size_t stream_id, std::shared_ptr& buf, size_t& buf_size); + + [[nodiscard]] size_t get_uncompressed_stream_size(size_t stream_id) const { + return m_stream_metadata.at(stream_id).uncompressed_size; + } + +private: + enum PackedStreamReaderState { + Uninitialized, + MetadataRead, + PackedStreamsOpened, + PackedStreamsOpenedAndMetadataRead, + ReadingPackedStreams + }; + + std::vector m_stream_metadata; + FileReader m_packed_stream_reader; + ZstdDecompressor m_packed_stream_decompressor; + PackedStreamReaderState m_state{PackedStreamReaderState::Uninitialized}; + size_t m_prev_stream_id{0ULL}; +}; + +} // namespace clp_s + +#endif // CLP_S_PACKEDSTREAMREADER_HPP diff --git a/components/core/src/clp_s/ReaderUtils.cpp b/components/core/src/clp_s/ReaderUtils.cpp index 11583a60d..a2ab5a34a 100644 --- a/components/core/src/clp_s/ReaderUtils.cpp +++ b/components/core/src/clp_s/ReaderUtils.cpp @@ -18,10 +18,10 @@ std::shared_ptr ReaderUtils::read_schema_tree(std::string const& arc throw OperationFailed(error_code, __FILENAME__, __LINE__); } + std::string key; for (size_t i = 0; i < num_nodes; i++) { int32_t parent_id; size_t key_length; - std::string key; uint8_t node_type; error_code = schema_tree_decompressor.try_read_numeric_value(parent_id); diff --git a/components/core/src/clp_s/ReaderUtils.hpp b/components/core/src/clp_s/ReaderUtils.hpp index 220947a21..caa509d6a 100644 --- a/components/core/src/clp_s/ReaderUtils.hpp +++ b/components/core/src/clp_s/ReaderUtils.hpp @@ -18,7 +18,7 @@ class ReaderUtils { : TraceableException(error_code, filename, line_number) {} }; - typedef std::map SchemaMap; + using SchemaMap = std::map; static constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB /** diff --git a/components/core/src/clp_s/SchemaReader.cpp b/components/core/src/clp_s/SchemaReader.cpp index 03edebf69..8120ab323 100644 --- a/components/core/src/clp_s/SchemaReader.cpp +++ b/components/core/src/clp_s/SchemaReader.cpp @@ -37,17 +37,20 @@ void SchemaReader::mark_column_as_timestamp(BaseColumnReader* column_reader) { } } -void SchemaReader::load(ZstdDecompressor& decompressor, size_t uncompressed_size) { - if (uncompressed_size > m_table_buffer_size) { - m_table_buffer = std::make_unique(uncompressed_size); - m_table_buffer_size = uncompressed_size; - } - auto error = decompressor.try_read_exact_length(m_table_buffer.get(), uncompressed_size); - if (ErrorCodeSuccess != error) { - throw OperationFailed(error, __FILENAME__, __LINE__); +int64_t SchemaReader::get_next_log_event_idx() const { + if (nullptr != m_log_event_idx_column) { + return std::get(m_log_event_idx_column->extract_value(m_cur_message)); } + return 0; +} - BufferViewReader buffer_reader{m_table_buffer.get(), uncompressed_size}; +void SchemaReader::load( + std::shared_ptr stream_buffer, + size_t offset, + size_t uncompressed_size +) { + m_stream_buffer = stream_buffer; + BufferViewReader buffer_reader{m_stream_buffer.get() + offset, uncompressed_size}; for (auto& reader : m_columns) { reader->load(buffer_reader, m_num_messages); } @@ -90,7 +93,7 @@ void SchemaReader::generate_json_string() { } case JsonSerializer::Op::AddIntField: { column = m_reordered_columns[column_id_index++]; - auto const& name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); + auto name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); m_json_serializer.append_key(name); m_json_serializer.append_value( std::to_string(std::get(column->extract_value(m_cur_message))) @@ -106,7 +109,7 @@ void SchemaReader::generate_json_string() { } case JsonSerializer::Op::AddFloatField: { column = m_reordered_columns[column_id_index++]; - auto const& name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); + auto name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); m_json_serializer.append_key(name); m_json_serializer.append_value( std::to_string(std::get(column->extract_value(m_cur_message))) @@ -122,7 +125,7 @@ void SchemaReader::generate_json_string() { } case JsonSerializer::Op::AddBoolField: { column = m_reordered_columns[column_id_index++]; - auto const& name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); + auto name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); m_json_serializer.append_key(name); m_json_serializer.append_value( std::get(column->extract_value(m_cur_message)) != 0 ? "true" @@ -140,7 +143,7 @@ void SchemaReader::generate_json_string() { } case JsonSerializer::Op::AddStringField: { column = m_reordered_columns[column_id_index++]; - auto const& name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); + auto name = m_global_schema_tree->get_node(column->get_id()).get_key_name(); m_json_serializer.append_key(name); m_json_serializer.append_value_from_column_with_quotes(column, m_cur_message); break; @@ -219,9 +222,10 @@ bool SchemaReader::get_next_message(std::string& message, FilterClass* filter) { return false; } -bool SchemaReader::get_next_message_with_timestamp( +bool SchemaReader::get_next_message_with_metadata( std::string& message, epochtime_t& timestamp, + int64_t& log_event_idx, FilterClass* filter ) { // TODO: If we already get max_num_results messages, we can skip messages @@ -245,6 +249,7 @@ bool SchemaReader::get_next_message_with_timestamp( } timestamp = m_get_timestamp(); + log_event_idx = get_next_log_event_idx(); m_cur_message++; return true; @@ -548,19 +553,25 @@ void SchemaReader::initialize_serializer() { m_serializer_initialized = true; for (int32_t global_column_id : m_ordered_schema) { - generate_local_tree(global_column_id); + if (m_projection->matches_node(global_column_id)) { + generate_local_tree(global_column_id); + } } for (auto it = m_global_id_to_unordered_object.begin(); it != m_global_id_to_unordered_object.end(); ++it) { - generate_local_tree(it->first); + if (m_projection->matches_node(it->first)) { + generate_local_tree(it->first); + } } // TODO: this code will have to change once we allow mixing log lines parsed by different // parsers. - generate_json_template(0); + if (false == m_local_schema_tree.get_nodes().empty()) { + generate_json_template(m_local_schema_tree.get_object_subtree_node_id()); + } } void SchemaReader::generate_json_template(int32_t id) { @@ -570,7 +581,7 @@ void SchemaReader::generate_json_template(int32_t id) { for (int32_t child_id : children_ids) { int32_t child_global_id = m_local_id_to_global_id[child_id]; auto const& child_node = m_local_schema_tree.get_node(child_id); - std::string const& key = child_node.get_key_name(); + auto key = child_node.get_key_name(); switch (child_node.get_type()) { case NodeType::Object: { m_json_serializer.add_op(JsonSerializer::Op::BeginObject); diff --git a/components/core/src/clp_s/SchemaReader.hpp b/components/core/src/clp_s/SchemaReader.hpp index 8597316a6..75f9ff117 100644 --- a/components/core/src/clp_s/SchemaReader.hpp +++ b/components/core/src/clp_s/SchemaReader.hpp @@ -1,6 +1,7 @@ #ifndef CLP_S_SCHEMAREADER_HPP #define CLP_S_SCHEMAREADER_HPP +#include #include #include #include @@ -11,6 +12,7 @@ #include "FileReader.hpp" #include "JsonSerializer.hpp" #include "SchemaTree.hpp" +#include "search/Projection.hpp" #include "ZstdDecompressor.hpp" namespace clp_s { @@ -47,10 +49,11 @@ class SchemaReader { : TraceableException(error_code, filename, line_number) {} }; - struct TableMetadata { + struct SchemaMetadata { + uint64_t stream_id; + uint64_t stream_offset; uint64_t num_messages; - size_t offset; - size_t uncompressed_size; + uint64_t uncompressed_size; }; // Constructor @@ -71,6 +74,7 @@ class SchemaReader { * to accept append_column calls for the new schema. * * @param schema_tree + * @param projection * @param schema_id * @param ordered_schema * @param num_messages @@ -78,6 +82,7 @@ class SchemaReader { */ void reset( std::shared_ptr schema_tree, + std::shared_ptr projection, int32_t schema_id, std::span ordered_schema, uint64_t num_messages, @@ -94,12 +99,14 @@ class SchemaReader { m_reordered_columns.clear(); m_timestamp_column = nullptr; m_get_timestamp = []() -> epochtime_t { return 0; }; + m_log_event_idx_column = nullptr; m_local_id_to_global_id.clear(); m_global_id_to_local_id.clear(); m_global_id_to_unordered_object.clear(); m_local_schema_tree.clear(); m_json_serializer.clear(); m_global_schema_tree = std::move(schema_tree); + m_projection = std::move(projection); m_should_marshal_records = should_marshal_records; } @@ -130,11 +137,12 @@ class SchemaReader { ); /** - * Loads the encoded messages - * @param decompressor + * Loads the encoded messages from a shared buffer starting at a given offset + * @param stream_buffer + * @param offset * @param uncompressed_size */ - void load(ZstdDecompressor& decompressor, size_t uncompressed_size); + void load(std::shared_ptr stream_buffer, size_t offset, size_t uncompressed_size); /** * Gets next message @@ -152,15 +160,17 @@ class SchemaReader { bool get_next_message(std::string& message, FilterClass* filter); /** - * Gets the next message matching a filter, and its timestamp + * Gets the next message matching a filter as well as its timestamp and log event index. * @param message * @param timestamp + * @param log_event_idx * @param filter * @return true if there is a next message */ - bool get_next_message_with_timestamp( + bool get_next_message_with_metadata( std::string& message, epochtime_t& timestamp, + int64_t& log_event_idx, FilterClass* filter ); @@ -176,6 +186,13 @@ class SchemaReader { */ void mark_column_as_timestamp(BaseColumnReader* column_reader); + /** + * Marks a column as the log_event_idx column. + */ + void mark_column_as_log_event_idx(Int64ColumnReader* column_reader) { + m_log_event_idx_column = column_reader; + } + int32_t get_schema_id() const { return m_schema_id; } /** @@ -185,6 +202,22 @@ class SchemaReader { */ static int32_t get_first_column_in_span(std::span schema); + /** + * @return the timestamp found in the row pointed to by m_cur_message + */ + epochtime_t get_next_timestamp() const { return m_get_timestamp(); } + + /** + * @return the log_event_idx in the row pointed to by m_cur_message or 0 if there is no + * log_event_idx in this table. + */ + int64_t get_next_log_event_idx() const; + + /** + * @return true if all records in this table have been iterated over, false otherwise + */ + bool done() const { return m_cur_message >= m_num_messages; } + private: /** * Merges the current local schema tree with the section of the global schema tree corresponding @@ -267,11 +300,11 @@ class SchemaReader { std::unordered_map m_column_map; std::vector m_columns; std::vector m_reordered_columns; - std::unique_ptr m_table_buffer; - size_t m_table_buffer_size{0}; + std::shared_ptr m_stream_buffer; BaseColumnReader* m_timestamp_column; std::function m_get_timestamp; + Int64ColumnReader* m_log_event_idx_column{nullptr}; std::shared_ptr m_global_schema_tree; SchemaTree m_local_schema_tree; @@ -281,6 +314,7 @@ class SchemaReader { JsonSerializer m_json_serializer; bool m_should_marshal_records{true}; bool m_serializer_initialized{false}; + std::shared_ptr m_projection; std::map>> m_global_id_to_unordered_object; }; diff --git a/components/core/src/clp_s/SchemaTree.cpp b/components/core/src/clp_s/SchemaTree.cpp index e56168a2c..4408efc01 100644 --- a/components/core/src/clp_s/SchemaTree.cpp +++ b/components/core/src/clp_s/SchemaTree.cpp @@ -5,9 +5,8 @@ #include "ZstdCompressor.hpp" namespace clp_s { -int32_t SchemaTree::add_node(int32_t parent_node_id, NodeType type, std::string const& key) { - std::tuple node_key = {parent_node_id, key, type}; - auto node_it = m_node_map.find(node_key); +int32_t SchemaTree::add_node(int32_t parent_node_id, NodeType type, std::string_view const key) { + auto node_it = m_node_map.find({parent_node_id, key, type}); if (node_it != m_node_map.end()) { auto node_id = node_it->second; m_nodes[node_id].increase_count(); @@ -15,18 +14,42 @@ int32_t SchemaTree::add_node(int32_t parent_node_id, NodeType type, std::string } int32_t node_id = m_nodes.size(); - auto& node = m_nodes.emplace_back(parent_node_id, node_id, key, type, 0); + auto& node = m_nodes.emplace_back(parent_node_id, node_id, std::string{key}, type, 0); node.increase_count(); - if (parent_node_id >= 0) { + if (constants::cRootNodeId == parent_node_id) { + if (NodeType::Object == type) { + m_object_subtree_id = node_id; + } else if (NodeType::Metadata == type) { + m_metadata_subtree_id = node_id; + } + } + + if (constants::cRootNodeId != parent_node_id) { auto& parent_node = m_nodes[parent_node_id]; node.set_depth(parent_node.get_depth() + 1); parent_node.add_child(node_id); } - m_node_map[node_key] = node_id; + m_node_map.emplace(std::make_tuple(parent_node_id, node.get_key_name(), type), node_id); return node_id; } +int32_t SchemaTree::get_metadata_field_id(std::string_view const field_name) { + if (m_metadata_subtree_id < 0) { + return -1; + } + + auto& metadata_subtree_node = m_nodes[m_metadata_subtree_id]; + for (auto child_id : metadata_subtree_node.get_children_ids()) { + auto& child_node = m_nodes[child_id]; + if (child_node.get_key_name() == field_name) { + return child_id; + } + } + + return -1; +} + size_t SchemaTree::store(std::string const& archives_dir, int compression_level) { FileWriter schema_tree_writer; ZstdCompressor schema_tree_compressor; @@ -41,9 +64,9 @@ size_t SchemaTree::store(std::string const& archives_dir, int compression_level) for (auto const& node : m_nodes) { schema_tree_compressor.write_numeric_value(node.get_parent_id()); - std::string const& key = node.get_key_name(); + auto key = node.get_key_name(); schema_tree_compressor.write_numeric_value(key.size()); - schema_tree_compressor.write_string(key); + schema_tree_compressor.write(key.begin(), key.size()); schema_tree_compressor.write_numeric_value(node.get_type()); } diff --git a/components/core/src/clp_s/SchemaTree.hpp b/components/core/src/clp_s/SchemaTree.hpp index 05ed52935..438714fa2 100644 --- a/components/core/src/clp_s/SchemaTree.hpp +++ b/components/core/src/clp_s/SchemaTree.hpp @@ -5,11 +5,25 @@ #include #include #include +#include #include #include namespace clp_s { +/** + * This enum defines the valid MPT node types as well as the 8-bit number used to encode them. + * + * The number used to represent each node type can not change. That means that elements in this + * enum can never be reordered and that new node types always need to be added to the end of the + * enum (but before Unknown). + * + * Node types are used to help record the structure of a log record, with the exception of the + * "Metadata" node type. The "Metadata" type is a special type used by the implementation to + * demarcate data needed by the implementation that is not part of the log record. In particular, + * the implementation may create a special subtree of the MPT which contains fields used to record + * things like original log order. + */ enum class NodeType : uint8_t { Integer, Float, @@ -21,21 +35,37 @@ enum class NodeType : uint8_t { NullValue, DateString, StructuredArray, + Metadata, Unknown = std::underlying_type::type(~0ULL) }; +/** + * This class represents a single node in the SchemaTree. + * + * Note: the result of get_key_name is valid even if the original SchemaNode is later + * move-constructed. + */ class SchemaNode { public: // Constructor SchemaNode() : m_parent_id(-1), m_id(-1), m_type(NodeType::Integer), m_count(0) {} - SchemaNode(int32_t parent_id, int32_t id, std::string key_name, NodeType type, int32_t depth) + SchemaNode( + int32_t parent_id, + int32_t id, + std::string_view const key_name, + NodeType type, + int32_t depth + ) : m_parent_id(parent_id), m_id(id), - m_key_name(std::move(key_name)), + m_key_name_buf(std::make_unique(key_name.size())), + m_key_name(m_key_name_buf.get(), key_name.size()), m_type(type), m_count(0), - m_depth(depth) {} + m_depth(depth) { + memcpy(m_key_name_buf.get(), key_name.begin(), key_name.size()); + } /** * Getters @@ -48,7 +78,7 @@ class SchemaNode { NodeType get_type() const { return m_type; } - std::string const& get_key_name() const { return m_key_name; } + std::string_view const get_key_name() const { return m_key_name; } int32_t get_count() const { return m_count; } @@ -71,7 +101,10 @@ class SchemaNode { int32_t m_id; int32_t m_parent_id; std::vector m_children_ids; - std::string m_key_name; + // We use a buffer so that references to this key name are stable after this SchemaNode is move + // constructed + std::unique_ptr m_key_name_buf; + std::string_view m_key_name; NodeType m_type; int32_t m_count; int32_t m_depth{0}; @@ -81,7 +114,7 @@ class SchemaTree { public: SchemaTree() = default; - int32_t add_node(int parent_node_id, NodeType type, std::string const& key); + int32_t add_node(int parent_node_id, NodeType type, std::string_view const key); bool has_node(int32_t id) { return id < m_nodes.size() && id >= 0; } @@ -93,7 +126,25 @@ class SchemaTree { return m_nodes[id]; } - int32_t get_root_node_id() const { return m_nodes[0].get_id(); } + /** + * @return the Id of the root of the Object sub-tree that records the structure of JSON data. + * @return -1 if the Object sub-tree does not exist. + */ + int32_t get_object_subtree_node_id() const { return m_object_subtree_id; } + + /** + * Get the field Id for a specified field within the Metadata subtree. + * @param field_name + * + * @return the field Id if the field exists within the Metadata sub-tree, -1 otherwise. + */ + int32_t get_metadata_field_id(std::string_view const field_name); + + /** + * @return the Id of the root of the Metadata sub-tree. + * @return -1 if the Metadata sub-tree does not exist. + */ + int32_t get_metadata_subtree_node_id() { return m_metadata_subtree_id; } std::vector const& get_nodes() const { return m_nodes; } @@ -129,7 +180,9 @@ class SchemaTree { private: std::vector m_nodes; - absl::flat_hash_map, int32_t> m_node_map; + absl::flat_hash_map, int32_t> m_node_map; + int32_t m_object_subtree_id{-1}; + int32_t m_metadata_subtree_id{-1}; }; } // namespace clp_s diff --git a/components/core/src/clp_s/SchemaWriter.cpp b/components/core/src/clp_s/SchemaWriter.cpp index b6de15f2f..ae3f16f15 100644 --- a/components/core/src/clp_s/SchemaWriter.cpp +++ b/components/core/src/clp_s/SchemaWriter.cpp @@ -4,35 +4,32 @@ namespace clp_s { void SchemaWriter::append_column(BaseColumnWriter* column_writer) { + m_total_uncompressed_size += column_writer->get_total_header_size(); m_columns.push_back(column_writer); } size_t SchemaWriter::append_message(ParsedMessage& message) { - int count = 0; - size_t size, total_size; - size = total_size = 0; + int count{}; + size_t total_size{}; for (auto& i : message.get_content()) { - m_columns[count]->add_value(i.second, size); - total_size += size; - count++; + total_size += m_columns[count]->add_value(i.second); + ++count; } for (auto& i : message.get_unordered_content()) { - m_columns[count]->add_value(i, size); - total_size += size; + total_size += m_columns[count]->add_value(i); ++count; } m_num_messages++; + m_total_uncompressed_size += total_size; return total_size; } -size_t SchemaWriter::store(ZstdCompressor& compressor) { - size_t total_size = 0; +void SchemaWriter::store(ZstdCompressor& compressor) { for (auto& writer : m_columns) { - total_size += writer->store(compressor); + writer->store(compressor); } - return total_size; } SchemaWriter::~SchemaWriter() { diff --git a/components/core/src/clp_s/SchemaWriter.hpp b/components/core/src/clp_s/SchemaWriter.hpp index 41b28600b..4f204d949 100644 --- a/components/core/src/clp_s/SchemaWriter.hpp +++ b/components/core/src/clp_s/SchemaWriter.hpp @@ -40,20 +40,19 @@ class SchemaWriter { /** * Stores the columns to disk. * @param compressor - * @return the uncompressed in-memory size of the table */ - [[nodiscard]] size_t store(ZstdCompressor& compressor); + void store(ZstdCompressor& compressor); + + uint64_t get_num_messages() const { return m_num_messages; } /** - * Closes the schema writer. - * @return the compressed size of the schema table in bytes + * @return the uncompressed in-memory size of the data that will be written to the compressor */ - [[nodiscard]] size_t close(); - - uint64_t get_num_messages() const { return m_num_messages; } + size_t get_total_uncompressed_size() const { return m_total_uncompressed_size; } private: uint64_t m_num_messages; + size_t m_total_uncompressed_size{}; std::vector m_columns; std::vector m_unordered_columns; diff --git a/components/core/src/clp_s/SingleFileArchiveDefs.hpp b/components/core/src/clp_s/SingleFileArchiveDefs.hpp new file mode 100644 index 000000000..7eabeb6db --- /dev/null +++ b/components/core/src/clp_s/SingleFileArchiveDefs.hpp @@ -0,0 +1,59 @@ +#ifndef CLP_S_ARCHIVEDEFS_HPP +#define CLP_S_ARCHIVEDEFS_HPP + +#include + +#include "msgpack.hpp" + +namespace clp_s { +// define the version +constexpr uint8_t cArchiveMajorVersion = 0; +constexpr uint8_t cArchiveMinorVersion = 2; +constexpr uint16_t cArchivePatchVersion = 0; + +// define the magic number +constexpr uint8_t cStructuredSFAMagicNumber[] = {0xFD, 0x2F, 0xC5, 0x30}; + +struct ArchiveHeader { + uint8_t magic_number[4]; + uint32_t version; + uint64_t uncompressed_size; + uint64_t compressed_size; + uint64_t reserved_padding[4]; + uint32_t metadata_section_size; + uint16_t compression_type; + uint16_t padding; +}; + +enum class ArchiveCompressionType : uint16_t { + Zstd = 0, +}; + +enum struct ArchiveMetadataPacketType : uint8_t { + ArchiveInfo = 0, + ArchiveFileInfo = 1, + TimestampDictionary = 2, +}; + +struct ArchiveInfoPacket { + uint64_t num_segments; + // TODO: Add more fields in the future + + MSGPACK_DEFINE_MAP(num_segments); +}; + +struct ArchiveFileInfo { + std::string n; // name + uint64_t o; // offset + + MSGPACK_DEFINE_MAP(n, o); +}; + +struct ArchiveFileInfoPacket { + std::vector files; + + MSGPACK_DEFINE_MAP(files); +}; +} // namespace clp_s + +#endif // CLP_S_ARCHIVEDEFS_HPP diff --git a/components/core/src/clp_s/TimestampDictionaryReader.cpp b/components/core/src/clp_s/TimestampDictionaryReader.cpp index c366e4f59..15685a97e 100644 --- a/components/core/src/clp_s/TimestampDictionaryReader.cpp +++ b/components/core/src/clp_s/TimestampDictionaryReader.cpp @@ -44,7 +44,9 @@ void TimestampDictionaryReader::read_new_entries() { TimestampEntry entry; std::vector tokens; entry.try_read_from_file(m_dictionary_decompressor); - StringUtils::tokenize_column_descriptor(entry.get_key_name(), tokens); + if (false == StringUtils::tokenize_column_descriptor(entry.get_key_name(), tokens)) { + throw OperationFailed(ErrorCodeCorrupt, __FILENAME__, __LINE__); + } m_entries.emplace_back(std::move(entry)); // TODO: Currently, we only allow a single authoritative timestamp column at ingestion time, diff --git a/components/core/src/clp_s/TimestampDictionaryReader.hpp b/components/core/src/clp_s/TimestampDictionaryReader.hpp index 132debfd4..3364c565c 100644 --- a/components/core/src/clp_s/TimestampDictionaryReader.hpp +++ b/components/core/src/clp_s/TimestampDictionaryReader.hpp @@ -73,9 +73,9 @@ class TimestampDictionaryReader { } private: - typedef std::map id_to_pattern_t; - typedef std::vector, TimestampEntry*>> - tokenized_column_to_range_t; + using id_to_pattern_t = std::map; + using tokenized_column_to_range_t + = std::vector, TimestampEntry*>>; // Variables bool m_is_open; diff --git a/components/core/src/clp_s/TimestampDictionaryWriter.cpp b/components/core/src/clp_s/TimestampDictionaryWriter.cpp index 7b02fd3a5..39e66a6af 100644 --- a/components/core/src/clp_s/TimestampDictionaryWriter.cpp +++ b/components/core/src/clp_s/TimestampDictionaryWriter.cpp @@ -1,63 +1,34 @@ #include "TimestampDictionaryWriter.hpp" +#include + #include "Utils.hpp" namespace clp_s { void TimestampDictionaryWriter::write_timestamp_entries( std::map const& ranges, - ZstdCompressor& compressor + std::stringstream& stream ) { - compressor.write_numeric_value(ranges.size()); + write_numeric_value(stream, ranges.size()); for (auto const& range : ranges) { - range.second.write_to_file(compressor); + range.second.write_to_stream(stream); } } -void TimestampDictionaryWriter::write_and_flush_to_disk() { - write_timestamp_entries(m_column_key_to_range, m_dictionary_compressor); +void TimestampDictionaryWriter::write(std::stringstream& stream) { + merge_range(); + write_timestamp_entries(m_column_key_to_range, stream); - m_dictionary_compressor.write_numeric_value(m_pattern_to_id.size()); + write_numeric_value(stream, m_pattern_to_id.size()); for (auto& it : m_pattern_to_id) { // write pattern ID - m_dictionary_compressor.write_numeric_value(it.second); + write_numeric_value(stream, it.second); std::string const& pattern = it.first->get_format(); - m_dictionary_compressor.write_numeric_value(pattern.length()); - m_dictionary_compressor.write_string(pattern); - } - - m_dictionary_compressor.flush(); - m_dictionary_file_writer.flush(); -} - -void TimestampDictionaryWriter::open(std::string const& dictionary_path, int compression_level) { - if (m_is_open) { - throw OperationFailed(ErrorCodeNotReady, __FILENAME__, __LINE__); - } - - m_dictionary_file_writer.open(dictionary_path, FileWriter::OpenMode::CreateForWriting); - m_dictionary_compressor.open(m_dictionary_file_writer, compression_level); - - m_next_id = 0; - m_is_open = true; -} - -size_t TimestampDictionaryWriter::close() { - if (false == m_is_open) { - throw OperationFailed(ErrorCodeNotInit, __FILENAME__, __LINE__); + write_numeric_value(stream, pattern.length()); + stream.write(pattern.data(), pattern.size()); } - - // merge before writing overall archive because this - // happens before the last sub-archive is written - merge_range(); - write_and_flush_to_disk(); - m_dictionary_compressor.close(); - size_t compressed_size = m_dictionary_file_writer.get_pos(); - m_dictionary_file_writer.close(); - - m_is_open = false; - return compressed_size; } uint64_t TimestampDictionaryWriter::get_pattern_id(TimestampPattern const* pattern) { @@ -180,4 +151,11 @@ epochtime_t TimestampDictionaryWriter::get_end_timestamp() const { return it->second.get_end_timestamp(); } + +void TimestampDictionaryWriter::clear() { + m_next_id = 0; + m_pattern_to_id.clear(); + m_column_key_to_range.clear(); + m_column_id_to_range.clear(); +} } // namespace clp_s diff --git a/components/core/src/clp_s/TimestampDictionaryWriter.hpp b/components/core/src/clp_s/TimestampDictionaryWriter.hpp index d040b0b56..29288fd48 100644 --- a/components/core/src/clp_s/TimestampDictionaryWriter.hpp +++ b/components/core/src/clp_s/TimestampDictionaryWriter.hpp @@ -1,15 +1,15 @@ #ifndef CLP_S_TIMESTAMPDICTIONARYWRITER_HPP #define CLP_S_TIMESTAMPDICTIONARYWRITER_HPP +#include +#include #include #include #include -#include "FileWriter.hpp" #include "SchemaTree.hpp" #include "TimestampEntry.hpp" #include "TimestampPattern.hpp" -#include "ZstdCompressor.hpp" namespace clp_s { class TimestampDictionaryWriter { @@ -23,25 +23,13 @@ class TimestampDictionaryWriter { }; // Constructors - TimestampDictionaryWriter() : m_is_open(false) {} + TimestampDictionaryWriter() {} /** - * Opens the timestamp dictionary for writing - * @param dictionary_path - * @param compression_level + * Writes the timestamp dictionary to a buffered stream. + * @param stream */ - void open(std::string const& dictionary_path, int compression_level); - - /** - * Closes the timestamp dictionary - * @return the compressed size of the global timestamp dictionary in bytes - */ - [[nodiscard]] size_t close(); - - /** - * Writes the timestamp dictionary to disk - */ - void write_and_flush_to_disk(); + void write(std::stringstream& stream); /** * Gets the pattern id for a given pattern @@ -91,33 +79,30 @@ class TimestampDictionaryWriter { */ epochtime_t get_end_timestamp() const; + /** + * Clears and resets all internal state. + */ + void clear(); + private: /** - * Merges timestamp ranges with the same key name + * Merges timestamp ranges with the same key name but different node ids. */ void merge_range(); /** - * Writes timestamp entries to the disk + * Writes timestamp entries to a buffered stream. * @param ranges * @param compressor */ static void write_timestamp_entries( std::map const& ranges, - ZstdCompressor& compressor + std::stringstream& stream ); - typedef std::unordered_map pattern_to_id_t; + using pattern_to_id_t = std::unordered_map; // Variables - bool m_is_open; - - // Variables related to on-disk storage - FileWriter m_dictionary_file_writer; - ZstdCompressor m_dictionary_compressor; - FileWriter m_dictionary_file_writer_local; - ZstdCompressor m_dictionary_compressor_local; - pattern_to_id_t m_pattern_to_id; uint64_t m_next_id{}; diff --git a/components/core/src/clp_s/TimestampEntry.cpp b/components/core/src/clp_s/TimestampEntry.cpp index 54b27d22e..19d422066 100644 --- a/components/core/src/clp_s/TimestampEntry.cpp +++ b/components/core/src/clp_s/TimestampEntry.cpp @@ -1,6 +1,9 @@ #include "TimestampEntry.hpp" #include +#include + +#include "Utils.hpp" namespace clp_s { void TimestampEntry::ingest_timestamp(epochtime_t timestamp) { @@ -54,21 +57,21 @@ void TimestampEntry::merge_range(TimestampEntry const& entry) { } } -void TimestampEntry::write_to_file(ZstdCompressor& compressor) const { - compressor.write_numeric_value(m_key_name.size()); - compressor.write_string(m_key_name); - compressor.write_numeric_value(m_column_ids.size()); +void TimestampEntry::write_to_stream(std::stringstream& stream) const { + write_numeric_value(stream, m_key_name.size()); + stream.write(m_key_name.data(), m_key_name.size()); + write_numeric_value(stream, m_column_ids.size()); for (auto const& id : m_column_ids) { - compressor.write_numeric_value(id); + write_numeric_value(stream, id); } - compressor.write_numeric_value(m_encoding); + write_numeric_value(stream, m_encoding); if (m_encoding == Epoch) { - compressor.write_numeric_value(m_epoch_start); - compressor.write_numeric_value(m_epoch_end); + write_numeric_value(stream, m_epoch_start); + write_numeric_value(stream, m_epoch_end); } else if (m_encoding == DoubleEpoch) { - compressor.write_numeric_value(m_epoch_start_double); - compressor.write_numeric_value(m_epoch_end_double); + write_numeric_value(stream, m_epoch_start_double); + write_numeric_value(stream, m_epoch_end_double); } } diff --git a/components/core/src/clp_s/TimestampEntry.hpp b/components/core/src/clp_s/TimestampEntry.hpp index ad40b4b89..326ed9d73 100644 --- a/components/core/src/clp_s/TimestampEntry.hpp +++ b/components/core/src/clp_s/TimestampEntry.hpp @@ -1,6 +1,7 @@ #ifndef CLP_S_TIMESTAMPENTRY_HPP #define CLP_S_TIMESTAMPENTRY_HPP +#include #include #include #include @@ -9,7 +10,6 @@ #include "ErrorCode.hpp" #include "search/FilterOperation.hpp" #include "Utils.hpp" -#include "ZstdCompressor.hpp" #include "ZstdDecompressor.hpp" using clp_s::search::FilterOperation; @@ -66,10 +66,10 @@ class TimestampEntry { void merge_range(TimestampEntry const& entry); /** - * Write the timestamp entry to a file + * Write the timestamp entry to a buffered stream. * @param compressor */ - void write_to_file(ZstdCompressor& compressor) const; + void write_to_stream(std::stringstream& stream) const; /** * Try to read the timestamp entry from a file diff --git a/components/core/src/clp_s/Utils.cpp b/components/core/src/clp_s/Utils.cpp index f429fb4a3..acee48851 100644 --- a/components/core/src/clp_s/Utils.cpp +++ b/components/core/src/clp_s/Utils.cpp @@ -427,18 +427,34 @@ bool StringUtils::convert_string_to_double(std::string const& raw, double& conve return true; } -void StringUtils::tokenize_column_descriptor( +bool StringUtils::tokenize_column_descriptor( std::string const& descriptor, std::vector& tokens ) { - // TODO: handle escaped . correctly - auto start = 0U; - auto end = descriptor.find('.'); - while (end != std::string::npos) { - tokens.push_back(descriptor.substr(start, end - start)); - start = end + 1; - end = descriptor.find('.', start); + // TODO: add support for unicode sequences e.g. \u263A + std::string cur_tok; + for (size_t cur = 0; cur < descriptor.size(); ++cur) { + if ('\\' == descriptor[cur]) { + ++cur; + if (cur >= descriptor.size()) { + return false; + } + } else if ('.' == descriptor[cur]) { + if (cur_tok.empty()) { + return false; + } + tokens.push_back(cur_tok); + cur_tok.clear(); + continue; + } + cur_tok.push_back(descriptor[cur]); } - tokens.push_back(descriptor.substr(start)); + + if (cur_tok.empty()) { + return false; + } + + tokens.push_back(cur_tok); + return true; } } // namespace clp_s diff --git a/components/core/src/clp_s/Utils.hpp b/components/core/src/clp_s/Utils.hpp index de33b7728..553f7e608 100644 --- a/components/core/src/clp_s/Utils.hpp +++ b/components/core/src/clp_s/Utils.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -211,9 +212,9 @@ class StringUtils { * Converts a string column descriptor delimited by '.' into a list of tokens * @param descriptor * @param tokens - * @return the list of tokens pushed into the 'tokens' parameter + * @return true if the descriptor was tokenized successfully, false otherwise */ - static void + [[nodiscard]] static bool tokenize_column_descriptor(std::string const& descriptor, std::vector& tokens); private: @@ -254,6 +255,17 @@ inline T2 bit_cast(T1 t1) { return t2; } +/** + * Writes a numeric value to a stringstream. + * @param stream + * @param value + * @tparam ValueType + */ +template +void write_numeric_value(std::stringstream& stream, ValueType value) { + stream.write(reinterpret_cast(&value), sizeof(value)); +} + /** * A span of memory where the underlying memory may not be aligned correctly for type T. * diff --git a/components/core/src/clp_s/archive_constants.hpp b/components/core/src/clp_s/archive_constants.hpp index d5a89d0bf..b76af2944 100644 --- a/components/core/src/clp_s/archive_constants.hpp +++ b/components/core/src/clp_s/archive_constants.hpp @@ -1,7 +1,12 @@ #ifndef CLP_S_ARCHIVE_CONSTANTS_HPP #define CLP_S_ARCHIVE_CONSTANTS_HPP +#include + namespace clp_s::constants { +// Single file archive +constexpr char cTmpPostfix[] = ".tmp"; + // Schema files constexpr char cArchiveSchemaMapFile[] = "/schema_ids"; constexpr char cArchiveSchemaTreeFile[] = "/schema_tree"; @@ -15,5 +20,28 @@ constexpr char cArchiveArrayDictFile[] = "/array.dict"; constexpr char cArchiveLogDictFile[] = "/log.dict"; constexpr char cArchiveTimestampDictFile[] = "/timestamp.dict"; constexpr char cArchiveVarDictFile[] = "/var.dict"; + +// Schema tree constants +constexpr char cRootNodeName[] = ""; +constexpr int32_t cRootNodeId = -1; +constexpr char cMetadataSubtreeName[] = ""; +constexpr char cLogEventIdxName[] = "log_event_idx"; + +namespace results_cache::decompression { +constexpr char cPath[]{"path"}; +constexpr char cOrigFileId[]{"orig_file_id"}; +constexpr char cBeginMsgIx[]{"begin_msg_ix"}; +constexpr char cEndMsgIx[]{"end_msg_ix"}; +constexpr char cIsLastIrChunk[]{"is_last_ir_chunk"}; +} // namespace results_cache::decompression + +namespace results_cache::search { +constexpr char cOrigFilePath[]{"orig_file_path"}; +constexpr char cLogEventIx[]{"log_event_ix"}; +constexpr char cTimestamp[]{"timestamp"}; +constexpr char cMessage[]{"message"}; +constexpr char cArchiveId[]{"archive_id"}; +} // namespace results_cache::search + } // namespace clp_s::constants #endif // CLP_S_ARCHIVE_CONSTANTS_HPP diff --git a/components/core/src/clp_s/clp-s.cpp b/components/core/src/clp_s/clp-s.cpp index b1dda7508..b76683caf 100644 --- a/components/core/src/clp_s/clp-s.cpp +++ b/components/core/src/clp_s/clp-s.cpp @@ -29,6 +29,7 @@ #include "search/OrOfAndForm.hpp" #include "search/Output.hpp" #include "search/OutputHandler.hpp" +#include "search/Projection.hpp" #include "search/SchemaMatch.hpp" #include "TimestampPattern.hpp" #include "TraceableException.hpp" @@ -39,6 +40,7 @@ using clp_s::cArchiveFormatDevelopmentVersionFlag; using clp_s::cEpochTimeMax; using clp_s::cEpochTimeMin; using clp_s::CommandLineArguments; +using clp_s::StringUtils; namespace { /** @@ -89,10 +91,13 @@ bool compress(CommandLineArguments const& command_line_arguments) { option.archives_dir = archives_dir.string(); option.target_encoded_size = command_line_arguments.get_target_encoded_size(); option.max_document_size = command_line_arguments.get_max_document_size(); + option.min_table_size = command_line_arguments.get_minimum_table_size(); option.compression_level = command_line_arguments.get_compression_level(); option.timestamp_key = command_line_arguments.get_timestamp_key(); option.print_archive_stats = command_line_arguments.print_archive_stats(); + option.single_file_archive = command_line_arguments.get_single_file_archive(); option.structurize_arrays = command_line_arguments.get_structurize_arrays(); + option.record_log_order = command_line_arguments.get_record_log_order(); auto const& db_config_container = command_line_arguments.get_metadata_db_config(); if (db_config_container.has_value()) { @@ -179,6 +184,28 @@ bool search_archive( return true; } + // Populate projection + auto projection = std::make_shared( + command_line_arguments.get_projection_columns().empty() + ? ProjectionMode::ReturnAllColumns + : ProjectionMode::ReturnSelectedColumns + ); + try { + for (auto const& column : command_line_arguments.get_projection_columns()) { + std::vector descriptor_tokens; + if (false == StringUtils::tokenize_column_descriptor(column, descriptor_tokens)) { + SPDLOG_ERROR("Can not tokenize invalid column: \"{}\"", column); + return false; + } + projection->add_column(ColumnDescriptor::create(descriptor_tokens)); + } + } catch (clp_s::TraceableException& e) { + SPDLOG_ERROR("{}", e.what()); + return false; + } + projection->resolve_columns(archive_reader->get_schema_tree()); + archive_reader->set_projection(projection); + std::unique_ptr output_handler; try { switch (command_line_arguments.get_output_handler_type()) { @@ -245,6 +272,7 @@ int main(int argc, char const* argv[]) { } clp_s::TimestampPattern::init(); + mongocxx::instance const mongocxx_instance{}; CommandLineArguments command_line_arguments("clp-s"); auto parsing_result = command_line_arguments.parse_arguments(argc, argv); @@ -269,12 +297,20 @@ int main(int argc, char const* argv[]) { return 1; } - clp_s::JsonConstructorOption option; + clp_s::JsonConstructorOption option{}; option.output_dir = command_line_arguments.get_output_dir(); + option.ordered = command_line_arguments.get_ordered_decompression(); + option.archives_dir = archives_dir; + option.target_ordered_chunk_size = command_line_arguments.get_target_ordered_chunk_size(); + if (false == command_line_arguments.get_mongodb_uri().empty()) { + option.metadata_db + = {command_line_arguments.get_mongodb_uri(), + command_line_arguments.get_mongodb_collection()}; + } try { auto const& archive_id = command_line_arguments.get_archive_id(); if (false == archive_id.empty()) { - option.archives_dir = std::filesystem::path{archives_dir} / archive_id; + option.archive_id = archive_id; decompress_archive(option); } else { for (auto const& entry : std::filesystem::directory_iterator(archives_dir)) { @@ -283,7 +319,7 @@ int main(int argc, char const* argv[]) { continue; } - option.archives_dir = entry.path(); + option.archive_id = entry.path().filename(); decompress_archive(option); } } @@ -292,8 +328,6 @@ int main(int argc, char const* argv[]) { return 1; } } else { - mongocxx::instance const mongocxx_instance{}; - auto const& query = command_line_arguments.get_query(); auto query_stream = std::istringstream(query); auto expr = kql::parse_kql_expression(query_stream); @@ -330,10 +364,7 @@ int main(int argc, char const* argv[]) { auto const& archive_id = command_line_arguments.get_archive_id(); auto archive_reader = std::make_shared(); if (false == archive_id.empty()) { - std::filesystem::path const archives_dir_path{archives_dir}; - std::string const archive_path{archives_dir_path / archive_id}; - - archive_reader->open(archive_path); + archive_reader->open(archives_dir, archive_id); if (false == search_archive(command_line_arguments, archive_reader, expr, reducer_socket_fd)) { @@ -347,7 +378,8 @@ int main(int argc, char const* argv[]) { continue; } - archive_reader->open(entry.path()); + auto const archive_id = entry.path().filename().string(); + archive_reader->open(archives_dir, archive_id); if (false == search_archive( command_line_arguments, diff --git a/components/core/src/clp_s/search/ColumnDescriptor.hpp b/components/core/src/clp_s/search/ColumnDescriptor.hpp index 0dc348817..ea1cfd7ec 100644 --- a/components/core/src/clp_s/search/ColumnDescriptor.hpp +++ b/components/core/src/clp_s/search/ColumnDescriptor.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "Literal.hpp" @@ -22,7 +23,7 @@ class DescriptorToken { * wildcards * @param token the string to initialize the token from */ - explicit DescriptorToken(std::string const& token) + explicit DescriptorToken(std::string_view const token) : m_token(token), m_wildcard(false), m_regex(false) { @@ -72,7 +73,7 @@ class DescriptorToken { std::string m_token; }; -typedef std::vector DescriptorList; +using DescriptorList = std::vector; DescriptorList tokenize_descriptor(std::vector const& descriptors); diff --git a/components/core/src/clp_s/search/Expression.hpp b/components/core/src/clp_s/search/Expression.hpp index 3b67bc16e..2919b75ae 100644 --- a/components/core/src/clp_s/search/Expression.hpp +++ b/components/core/src/clp_s/search/Expression.hpp @@ -8,7 +8,7 @@ #include "Value.hpp" namespace clp_s::search { -typedef std::list> OpList; +using OpList = std::list>; /** * Top level class for all logical expressions which represent filters diff --git a/components/core/src/clp_s/search/Integral.hpp b/components/core/src/clp_s/search/Integral.hpp index 0f54aff80..b46870c32 100644 --- a/components/core/src/clp_s/search/Integral.hpp +++ b/components/core/src/clp_s/search/Integral.hpp @@ -8,7 +8,7 @@ #include "Literal.hpp" namespace clp_s::search { -typedef std::variant Integral64; +using Integral64 = std::variant; // FIXME: figure out why String types are part of this bitmask constexpr LiteralTypeBitmask cIntegralLiteralTypes = cIntegralTypes | VarStringT; diff --git a/components/core/src/clp_s/search/Literal.hpp b/components/core/src/clp_s/search/Literal.hpp index 8374e01e6..af9b9b2f0 100644 --- a/components/core/src/clp_s/search/Literal.hpp +++ b/components/core/src/clp_s/search/Literal.hpp @@ -24,7 +24,7 @@ enum LiteralType : uint32_t { UnknownT = ((uint32_t)1) << 31 }; -typedef uint32_t LiteralTypeBitmask; +using LiteralTypeBitmask = uint32_t; constexpr LiteralTypeBitmask cIntegralTypes = LiteralType::IntegerT | LiteralType::FloatT; constexpr LiteralTypeBitmask cAllTypes = TypesEnd - 1; diff --git a/components/core/src/clp_s/search/OrOfAndForm.hpp b/components/core/src/clp_s/search/OrOfAndForm.hpp index 7a400eb3f..acc8254a2 100644 --- a/components/core/src/clp_s/search/OrOfAndForm.hpp +++ b/components/core/src/clp_s/search/OrOfAndForm.hpp @@ -8,8 +8,8 @@ #include "Transformation.hpp" namespace clp_s::search { -typedef std::vector> ExpressionVector; -typedef std::list> ExpressionList; +using ExpressionVector = std::vector>; +using ExpressionList = std::list>; // TODO: handle degenerate forms like empty or/and expressions class OrOfAndForm : public Transformation { diff --git a/components/core/src/clp_s/search/Output.cpp b/components/core/src/clp_s/search/Output.cpp index 524c055fd..c9954779b 100644 --- a/components/core/src/clp_s/search/Output.cpp +++ b/components/core/src/clp_s/search/Output.cpp @@ -62,9 +62,11 @@ bool Output::filter() { } } + populate_internal_columns(); populate_string_queries(top_level_expr); std::string message; + auto const archive_id = m_archive_reader->get_archive_id(); for (int32_t schema_id : matched_schemas) { m_expr_clp_query.clear(); m_expr_var_match_map.clear(); @@ -83,17 +85,18 @@ bool Output::filter() { add_wildcard_columns_to_searched_columns(); - auto& reader = m_archive_reader->read_table( + auto& reader = m_archive_reader->read_schema_table( schema_id, - m_output_handler->should_output_timestamp(), + m_output_handler->should_output_metadata(), m_should_marshal_records ); reader.initialize_filter(this); - if (m_output_handler->should_output_timestamp()) { - epochtime_t timestamp; - while (reader.get_next_message_with_timestamp(message, timestamp, this)) { - m_output_handler->write(message, timestamp); + if (m_output_handler->should_output_metadata()) { + epochtime_t timestamp{}; + int64_t log_event_idx{}; + while (reader.get_next_message_with_metadata(message, timestamp, log_event_idx, this)) { + m_output_handler->write(message, timestamp, archive_id, log_event_idx); } } else { while (reader.get_next_message(message, this)) { @@ -135,6 +138,10 @@ void Output::init( for (auto column_reader : column_readers) { auto column_id = column_reader->get_id(); + if (0 != m_metadata_columns.count(column_id)) { + continue; + } + if ((0 != (m_wildcard_type_mask & node_to_literal_type(m_schema_tree->get_node(column_id).get_type()))) @@ -958,6 +965,19 @@ void Output::populate_string_queries(std::shared_ptr const& expr) { } } +void Output::populate_internal_columns() { + int32_t metadata_subtree_root_node_id = m_schema_tree->get_metadata_subtree_node_id(); + if (-1 == metadata_subtree_root_node_id) { + return; + } + + // This code assumes that the metadata subtree contains no nested structures + auto& metadata_node = m_schema_tree->get_node(metadata_subtree_root_node_id); + for (auto child_id : metadata_node.get_children_ids()) { + m_metadata_columns.insert(child_id); + } +} + void Output::populate_searched_wildcard_columns(std::shared_ptr const& expr) { if (expr->has_only_expression_operands()) { for (auto const& op : expr->get_op_list()) { @@ -974,6 +994,9 @@ void Output::populate_searched_wildcard_columns(std::shared_ptr cons if (Schema::schema_entry_is_unordered_object(node)) { continue; } + if (0 != m_metadata_columns.count(node)) { + continue; + } auto tree_node_type = m_schema_tree->get_node(node).get_type(); if (col->matches_type(node_to_literal_type(tree_node_type))) { auto literal_type = node_to_literal_type(tree_node_type); diff --git a/components/core/src/clp_s/search/Output.hpp b/components/core/src/clp_s/search/Output.hpp index ba4c4e1d7..bfd364457 100644 --- a/components/core/src/clp_s/search/Output.hpp +++ b/components/core/src/clp_s/search/Output.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,7 @@ class Output : public FilterClass { std::vector m_wildcard_columns; std::map> m_wildcard_to_searched_basic_columns; LiteralTypeBitmask m_wildcard_type_mask{0}; + std::unordered_set m_metadata_columns; std::stack< std::pair, @@ -342,6 +344,11 @@ class Output : public FilterClass { */ void populate_string_queries(std::shared_ptr const& expr); + /** + * Populates the set of internal columns that get ignored during dynamic wildcard expansion. + */ + void populate_internal_columns(); + /** * Constant propagates an expression * @param expr diff --git a/components/core/src/clp_s/search/OutputHandler.cpp b/components/core/src/clp_s/search/OutputHandler.cpp index 47a66bbd5..13771e9b5 100644 --- a/components/core/src/clp_s/search/OutputHandler.cpp +++ b/components/core/src/clp_s/search/OutputHandler.cpp @@ -1,6 +1,8 @@ #include "OutputHandler.hpp" #include +#include +#include #include @@ -8,10 +10,14 @@ #include "../../reducer/CountOperator.hpp" #include "../../reducer/network_utils.hpp" #include "../../reducer/Record.hpp" +#include "../archive_constants.hpp" + +using std::string; +using std::string_view; namespace clp_s::search { NetworkOutputHandler::NetworkOutputHandler( - std::string const& host, + string const& host, int port, bool should_output_timestamp ) @@ -23,18 +29,15 @@ NetworkOutputHandler::NetworkOutputHandler( } } -void NetworkOutputHandler::write(std::string const& message, epochtime_t timestamp) { - msgpack::type::tuple src(timestamp, message, ""); - msgpack::sbuffer m; - msgpack::pack(m, src); - - if (-1 == send(m_socket_fd, m.data(), m.size(), 0)) { - throw OperationFailed(ErrorCode::ErrorCodeFailureNetwork, __FILE__, __LINE__); - } -} - -void NetworkOutputHandler::write(std::string const& message) { - msgpack::type::tuple src(0, message, ""); +void NetworkOutputHandler::write( + string_view message, + epochtime_t timestamp, + string_view archive_id, + int64_t log_event_idx +) { + static constexpr string_view cOrigFilePathPlaceholder{""}; + msgpack::type::tuple const + src(timestamp, message, cOrigFilePathPlaceholder, archive_id, log_event_idx); msgpack::sbuffer m; msgpack::pack(m, src); @@ -44,8 +47,8 @@ void NetworkOutputHandler::write(std::string const& message) { } ResultsCacheOutputHandler::ResultsCacheOutputHandler( - std::string const& uri, - std::string const& collection, + string const& uri, + string const& collection, uint64_t batch_size, uint64_t max_num_results, bool should_output_timestamp @@ -71,9 +74,26 @@ ErrorCode ResultsCacheOutputHandler::flush() { try { m_results.emplace_back(std::move(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("original_path", std::move(result.original_path)), - bsoncxx::builder::basic::kvp("message", std::move(result.message)), - bsoncxx::builder::basic::kvp("timestamp", result.timestamp) + bsoncxx::builder::basic::kvp( + constants::results_cache::search::cOrigFilePath, + std::move(result.original_path) + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::search::cMessage, + std::move(result.message) + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::search::cTimestamp, + result.timestamp + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::search::cArchiveId, + std::move(result.archive_id) + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::search::cLogEventIx, + result.log_event_idx + ) ))); count++; @@ -98,12 +118,29 @@ ErrorCode ResultsCacheOutputHandler::flush() { return ErrorCode::ErrorCodeSuccess; } -void ResultsCacheOutputHandler::write(std::string const& message, epochtime_t timestamp) { +void ResultsCacheOutputHandler::write( + string_view message, + epochtime_t timestamp, + string_view archive_id, + int64_t log_event_idx +) { if (m_latest_results.size() < m_max_num_results) { - m_latest_results.emplace(std::make_unique("", message, timestamp)); + m_latest_results.emplace(std::make_unique( + string_view{}, + message, + timestamp, + archive_id, + log_event_idx + )); } else if (m_latest_results.top()->timestamp < timestamp) { m_latest_results.pop(); - m_latest_results.emplace(std::make_unique("", message, timestamp)); + m_latest_results.emplace(std::make_unique( + string_view{}, + message, + timestamp, + archive_id, + log_event_idx + )); } } @@ -114,7 +151,7 @@ CountOutputHandler::CountOutputHandler(int reducer_socket_fd) m_pipeline.add_pipeline_stage(std::make_shared()); } -void CountOutputHandler::write(std::string const& message) { +void CountOutputHandler::write(string_view message) { m_pipeline.push_record(reducer::EmptyRecord{}); } diff --git a/components/core/src/clp_s/search/OutputHandler.hpp b/components/core/src/clp_s/search/OutputHandler.hpp index 624c006be..f033d37e8 100644 --- a/components/core/src/clp_s/search/OutputHandler.hpp +++ b/components/core/src/clp_s/search/OutputHandler.hpp @@ -5,8 +5,10 @@ #include #include +#include #include #include +#include #include #include @@ -28,8 +30,8 @@ namespace clp_s::search { class OutputHandler { public: // Constructors - explicit OutputHandler(bool should_output_timestamp, bool should_marshal_records) - : m_should_output_timestamp(should_output_timestamp), + explicit OutputHandler(bool should_output_metadata, bool should_marshal_records) + : m_should_output_metadata(should_output_metadata), m_should_marshal_records(should_marshal_records) {}; // Destructor @@ -40,14 +42,21 @@ class OutputHandler { * Writes a log event to the output handler. * @param message The message in the log event. * @param timestamp The timestamp of the log event. + * @param archive_id The archive containing the log event. + * @param log_event_idx The index of the log event within an archive. */ - virtual void write(std::string const& message, epochtime_t timestamp) = 0; + virtual void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) = 0; /** * Writes a message to the output handler. * @param message The message to write. */ - virtual void write(std::string const& message) = 0; + virtual void write(std::string_view message) = 0; /** * Flushes the output handler after each table that gets searched. @@ -61,12 +70,12 @@ class OutputHandler { */ virtual ErrorCode finish() { return ErrorCode::ErrorCodeSuccess; } - [[nodiscard]] bool should_output_timestamp() const { return m_should_output_timestamp; } + [[nodiscard]] bool should_output_metadata() const { return m_should_output_metadata; } [[nodiscard]] bool should_marshal_records() const { return m_should_marshal_records; } private: - bool m_should_output_timestamp; + bool m_should_output_metadata; bool m_should_marshal_records; }; @@ -76,15 +85,20 @@ class OutputHandler { class StandardOutputHandler : public OutputHandler { public: // Constructors - explicit StandardOutputHandler(bool should_output_timestamp = false) - : OutputHandler(should_output_timestamp, true) {} + explicit StandardOutputHandler(bool should_output_metadata = false) + : OutputHandler(should_output_metadata, true) {} // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override { - printf("%" EPOCHTIME_T_PRINTF_FMT " %s", timestamp, message.c_str()); + void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) override { + std::cout << archive_id << ": " << log_event_idx << ": " << timestamp << " " << message; } - void write(std::string const& message) override { printf("%s", message.c_str()); } + void write(std::string_view message) override { std::cout << message; } }; /** @@ -104,7 +118,7 @@ class NetworkOutputHandler : public OutputHandler { explicit NetworkOutputHandler( std::string const& host, int port, - bool should_output_timestamp = false + bool should_output_metadata = false ); // Destructor @@ -115,9 +129,14 @@ class NetworkOutputHandler : public OutputHandler { } // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override; + void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) override; - void write(std::string const& message) override; + void write(std::string_view message) override { write(message, 0, {}, 0); } private: std::string m_host; @@ -133,14 +152,24 @@ class ResultsCacheOutputHandler : public OutputHandler { // Types struct QueryResult { // Constructors - QueryResult(std::string original_path, std::string message, epochtime_t timestamp) - : original_path(std::move(original_path)), - message(std::move(message)), - timestamp(timestamp) {} + QueryResult( + std::string_view original_path, + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) + : original_path(original_path), + message(message), + timestamp(timestamp), + archive_id(archive_id), + log_event_idx(log_event_idx) {} std::string original_path; std::string message; epochtime_t timestamp; + std::string archive_id; + int64_t log_event_idx; }; struct QueryResultGreaterTimestampComparator { @@ -165,7 +194,7 @@ class ResultsCacheOutputHandler : public OutputHandler { std::string const& collection, uint64_t batch_size, uint64_t max_num_results, - bool should_output_timestamp = true + bool should_output_metadata = true ); // Methods inherited from OutputHandler @@ -176,9 +205,14 @@ class ResultsCacheOutputHandler : public OutputHandler { */ ErrorCode flush() override; - void write(std::string const& message, epochtime_t timestamp) override; + void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) override; - void write(std::string const& message) override { write(message, 0); } + void write(std::string_view message) override { write(message, 0, {}, 0); } private: mongocxx::client m_client; @@ -202,9 +236,14 @@ class CountOutputHandler : public OutputHandler { CountOutputHandler(int reducer_socket_fd); // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override {} + void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) override {} - void write(std::string const& message) override; + void write(std::string_view message) override; /** * Flushes the count. @@ -231,12 +270,17 @@ class CountByTimeOutputHandler : public OutputHandler { m_count_by_time_bucket_size{count_by_time_bucket_size} {} // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override { + void write( + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id, + int64_t log_event_idx + ) override { int64_t bucket = (timestamp / m_count_by_time_bucket_size) * m_count_by_time_bucket_size; m_bucket_counts[bucket] += 1; } - void write(std::string const& message) override {} + void write(std::string_view message) override {} /** * Flushes the counts. diff --git a/components/core/src/clp_s/search/Projection.cpp b/components/core/src/clp_s/search/Projection.cpp new file mode 100644 index 000000000..b1c453776 --- /dev/null +++ b/components/core/src/clp_s/search/Projection.cpp @@ -0,0 +1,88 @@ +#include "Projection.hpp" + +#include + +#include "SearchUtils.hpp" + +namespace clp_s::search { +void Projection::add_column(std::shared_ptr column) { + if (column->is_unresolved_descriptor()) { + throw OperationFailed(ErrorCodeBadParam, __FILE__, __LINE__); + } + if (ProjectionMode::ReturnAllColumns == m_projection_mode) { + throw OperationFailed(ErrorCodeUnsupported, __FILE__, __LINE__); + } + if (m_selected_columns.end() + != std::find_if( + m_selected_columns.begin(), + m_selected_columns.end(), + [column](auto const& rhs) -> bool { return *column == *rhs; } + )) + { + // no duplicate columns in projection + throw OperationFailed(ErrorCodeBadParam, __FILE__, __LINE__); + } + m_selected_columns.push_back(column); +} + +void Projection::resolve_columns(std::shared_ptr tree) { + for (auto& column : m_selected_columns) { + resolve_column(tree, column); + } +} + +void Projection::resolve_column( + std::shared_ptr tree, + std::shared_ptr column +) { + /** + * Ideally we would reuse the code from SchemaMatch for resolving columns, but unfortunately we + * can not. + * + * The main reason is that here we don't want to allow projection to travel inside unstructured + * objects -- it may be possible to support such a thing in the future, but it poses some extra + * challenges (e.g. deciding what to do when projecting repeated elements in a structure). + * + * It would be possible to create code that can handle our use-case and SchemaMatch's use-case + * in an elegant way, but it's a significant refactor. In particular, if we extend our column + * type system to be one-per-token instead of one-per-column we can make it so that intermediate + * tokens will not match certain kinds of MPT nodes (like the node for structured arrays). + * + * In light of that we implement a simple version of column resolution here that does exactly + * what we need. + */ + + auto cur_node_id = tree->get_object_subtree_node_id(); + auto it = column->descriptor_begin(); + while (it != column->descriptor_end()) { + bool matched_any{false}; + auto cur_it = it++; + bool last_token = it == column->descriptor_end(); + auto const& cur_node = tree->get_node(cur_node_id); + for (int32_t child_node_id : cur_node.get_children_ids()) { + auto const& child_node = tree->get_node(child_node_id); + + // Intermediate nodes must be objects + if (false == last_token && child_node.get_type() != NodeType::Object) { + continue; + } + + if (child_node.get_key_name() != cur_it->get_token()) { + continue; + } + + matched_any = true; + if (last_token && column->matches_type(node_to_literal_type(child_node.get_type()))) { + m_matching_nodes.insert(child_node_id); + } else if (false == last_token) { + cur_node_id = child_node_id; + break; + } + } + + if (false == matched_any) { + break; + } + } +} +} // namespace clp_s::search diff --git a/components/core/src/clp_s/search/Projection.hpp b/components/core/src/clp_s/search/Projection.hpp new file mode 100644 index 000000000..74fece742 --- /dev/null +++ b/components/core/src/clp_s/search/Projection.hpp @@ -0,0 +1,82 @@ +#ifndef CLP_S_SEARCH_PROJECTION_HPP +#define CLP_S_SEARCH_PROJECTION_HPP + +#include + +#include + +#include "../SchemaTree.hpp" +#include "../TraceableException.hpp" +#include "ColumnDescriptor.hpp" + +namespace clp_s::search { +enum ProjectionMode : uint8_t { + ReturnAllColumns, + ReturnSelectedColumns +}; + +/** + * This class describes the set of columns that should be included in the projected results. + * + * After adding columns and before calling matches_node the caller is responsible for calling + * resolve_columns. + */ +class Projection { +public: + class OperationFailed : public TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : TraceableException(error_code, filename, line_number) {} + }; + + explicit Projection(ProjectionMode mode) : m_projection_mode{mode} {} + + /** + * Adds a column to the set of columns that should be included in the projected results + * @param column + * @throws OperationFailed if `column` contains a wildcard + * @throws OperationFailed if this instance of Projection is in mode ReturnAllColumns + * @throws OperationFailed if `column` is identical to a previously added column + */ + void add_column(std::shared_ptr column); + + /** + * Resolves all columns for the purpose of projection. This key resolution implementation is + * more limited than the one in schema matching. In particular, this version of key resolution + * only allows resolving keys that do not contain wildcards and does not allow resolving to + * objects within arrays. + * + * Note: we could try to generalize column resolution code/move it to the schema tree. It is + * probably best to write a simpler version dedicated to projection for now since types are + * leaf-only. The type-per-token idea solves this problem (in the absence of wildcards). + * + * @param tree + */ + void resolve_columns(std::shared_ptr tree); + + /** + * Checks whether a column corresponding to given leaf node should be included in the output + * @param node_id + * @return true if the column should be included in the output, false otherwise + */ + bool matches_node(int32_t node_id) const { + return ProjectionMode::ReturnAllColumns == m_projection_mode + || m_matching_nodes.contains(node_id); + } + +private: + /** + * Resolves an individual column as described by the `resolve_columns` method. + * @param tree + * @param column + */ + void resolve_column(std::shared_ptr tree, std::shared_ptr column); + + std::vector> m_selected_columns; + absl::flat_hash_set m_matching_nodes; + ProjectionMode m_projection_mode{ProjectionMode::ReturnAllColumns}; +}; +} // namespace clp_s::search + +#endif // CLP_S_SEARCH_PROJECTION_HPP diff --git a/components/core/src/clp_s/search/SchemaMatch.cpp b/components/core/src/clp_s/search/SchemaMatch.cpp index 82c9af866..203634000 100644 --- a/components/core/src/clp_s/search/SchemaMatch.cpp +++ b/components/core/src/clp_s/search/SchemaMatch.cpp @@ -76,7 +76,7 @@ std::shared_ptr SchemaMatch::populate_column_mapping(std::shared_ptr auto const* node = &m_tree->get_node(node_id); auto literal_type = node_to_literal_type(node->get_type()); DescriptorList descriptors; - while (node->get_id() != m_tree->get_root_node_id()) { + while (node->get_id() != m_tree->get_object_subtree_node_id()) { // may have to explicitly mark non-regex descriptors.emplace_back(node->get_key_name()); node = &m_tree->get_node(node->get_parent_id()); @@ -111,9 +111,9 @@ bool SchemaMatch::populate_column_mapping(ColumnDescriptor* column) { return matched; } - // TODO: once we start supporting multi-rooted MPTs this (and anything that uses - // get_root_node_id, or assumes root node id is 0) will have to change - auto const& root = m_tree->get_node(m_tree->get_root_node_id()); + // TODO: Once we start supporting mixing different types of logs we will have to match against + // more than just the object subtree. + auto const& root = m_tree->get_node(m_tree->get_object_subtree_node_id()); for (int32_t child_node_id : root.get_children_ids()) { matched |= populate_column_mapping(column, child_node_id); } diff --git a/components/core/src/clp_s/search/SearchUtils.cpp b/components/core/src/clp_s/search/SearchUtils.cpp index 3f7c522cb..39ae694a0 100644 --- a/components/core/src/clp_s/search/SearchUtils.cpp +++ b/components/core/src/clp_s/search/SearchUtils.cpp @@ -34,6 +34,7 @@ LiteralType node_to_literal_type(NodeType type) { return LiteralType::NullT; case NodeType::DateString: return LiteralType::EpochDateT; + case NodeType::Metadata: case NodeType::Unknown: default: return LiteralType::UnknownT; diff --git a/components/core/src/clp_s/search/kql/Kql.g4 b/components/core/src/clp_s/search/kql/Kql.g4 index 2ddef732c..33abf66bd 100644 --- a/components/core/src/clp_s/search/kql/Kql.g4 +++ b/components/core/src/clp_s/search/kql/Kql.g4 @@ -96,7 +96,7 @@ fragment ESCAPED_SPACE ; fragment SPECIAL_CHARACTER - : [\\():<>"*?{}] + : [\\():<>"*?{}.] ; diff --git a/components/core/src/clp_s/search/kql/kql.cpp b/components/core/src/clp_s/search/kql/kql.cpp index fa560cef5..972e44ad7 100644 --- a/components/core/src/clp_s/search/kql/kql.cpp +++ b/components/core/src/clp_s/search/kql/kql.cpp @@ -112,7 +112,10 @@ class ParseTreeVisitor : public KqlBaseVisitor { std::string column = unquote_string(ctx->LITERAL()->getText()); std::vector descriptor_tokens; - StringUtils::tokenize_column_descriptor(column, descriptor_tokens); + if (false == StringUtils::tokenize_column_descriptor(column, descriptor_tokens)) { + SPDLOG_ERROR("Can not tokenize invalid column: \"{}\"", column); + return nullptr; + } return ColumnDescriptor::create(descriptor_tokens); } @@ -248,6 +251,10 @@ std::shared_ptr parse_kql_expression(std::istream& in) { } ParseTreeVisitor visitor; - return std::any_cast>(visitor.visitStart(tree)); + try { + return std::any_cast>(visitor.visitStart(tree)); + } catch (std::exception& e) { + return {}; + } } } // namespace clp_s::search::kql diff --git a/components/core/src/glt/Defs.h b/components/core/src/glt/Defs.h index 82517d32c..40429489b 100644 --- a/components/core/src/glt/Defs.h +++ b/components/core/src/glt/Defs.h @@ -8,21 +8,21 @@ namespace glt { // Types -typedef int64_t epochtime_t; +using epochtime_t = int64_t; constexpr epochtime_t cEpochTimeMin = std::numeric_limits::min(); constexpr epochtime_t cEpochTimeMax = std::numeric_limits::max(); #define SECONDS_TO_EPOCHTIME(x) x * 1000 #define MICROSECONDS_TO_EPOCHTIME(x) 0 -typedef uint64_t variable_dictionary_id_t; +using variable_dictionary_id_t = uint64_t; constexpr variable_dictionary_id_t cVariableDictionaryIdMax = std::numeric_limits::max(); -typedef int64_t logtype_dictionary_id_t; +using logtype_dictionary_id_t = int64_t; constexpr logtype_dictionary_id_t cLogtypeDictionaryIdMax = std::numeric_limits::max(); -typedef uint16_t archive_format_version_t; +using archive_format_version_t = uint16_t; // This flag is used to maintain two separate streams of archive format // versions: // - Development versions (which can change frequently as necessary) which @@ -31,19 +31,19 @@ typedef uint16_t archive_format_version_t; // as possible) which should not have the flag constexpr archive_format_version_t cArchiveFormatDevVersionFlag = 0x8000; -typedef uint32_t file_id_t; -typedef uint64_t segment_id_t; +using file_id_t = uint32_t; +using segment_id_t = uint64_t; constexpr segment_id_t cInvalidSegmentId = std::numeric_limits::max(); -typedef size_t offset_t; -typedef int64_t encoded_variable_t; -typedef uint64_t combined_table_id_t; +using offset_t = size_t; +using encoded_variable_t = int64_t; +using combined_table_id_t = uint64_t; -typedef uint64_t group_id_t; +using group_id_t = uint64_t; -typedef uint64_t pipeline_id_t; +using pipeline_id_t = uint64_t; constexpr pipeline_id_t cPipelineIdMax = std::numeric_limits::max(); -typedef std::atomic_uint64_t atomic_pipeline_id_t; +using atomic_pipeline_id_t = std::atomic_uint64_t; // Macros // Rounds up VALUE to be a multiple of MULTIPLE diff --git a/components/core/src/glt/DictionaryWriter.hpp b/components/core/src/glt/DictionaryWriter.hpp index cbab4184b..ccd4b49dd 100644 --- a/components/core/src/glt/DictionaryWriter.hpp +++ b/components/core/src/glt/DictionaryWriter.hpp @@ -98,7 +98,7 @@ class DictionaryWriter { protected: // Types - typedef std::unordered_map value_to_id_t; + using value_to_id_t = std::unordered_map; // Variables bool m_is_open; diff --git a/components/core/src/glt/Grep.hpp b/components/core/src/glt/Grep.hpp index 240859d41..1ec319f62 100644 --- a/components/core/src/glt/Grep.hpp +++ b/components/core/src/glt/Grep.hpp @@ -20,7 +20,7 @@ class Grep { * @param decompressed_msg * @param custom_arg Custom argument for the output function */ - typedef void (*OutputFunc)( + using OutputFunc = void (*)( std::string const& orig_file_path, streaming_archive::reader::Message const& compressed_msg, std::string const& decompressed_msg, diff --git a/components/core/src/glt/MySQLDB.hpp b/components/core/src/glt/MySQLDB.hpp index 4045fce12..97a5d33f5 100644 --- a/components/core/src/glt/MySQLDB.hpp +++ b/components/core/src/glt/MySQLDB.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "Defs.h" #include "ErrorCode.hpp" diff --git a/components/core/src/glt/MySQLParamBindings.hpp b/components/core/src/glt/MySQLParamBindings.hpp index 754b4401f..abfe8d3a1 100644 --- a/components/core/src/glt/MySQLParamBindings.hpp +++ b/components/core/src/glt/MySQLParamBindings.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "ErrorCode.hpp" #include "TraceableException.hpp" diff --git a/components/core/src/glt/MySQLPreparedStatement.hpp b/components/core/src/glt/MySQLPreparedStatement.hpp index c6cd0e390..ec68dda59 100644 --- a/components/core/src/glt/MySQLPreparedStatement.hpp +++ b/components/core/src/glt/MySQLPreparedStatement.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "ErrorCode.hpp" #include "MySQLParamBindings.hpp" diff --git a/components/core/src/glt/ir/LogEventDeserializer.cpp b/components/core/src/glt/ir/LogEventDeserializer.cpp index 00cd880e9..c22f22bb7 100644 --- a/components/core/src/glt/ir/LogEventDeserializer.cpp +++ b/components/core/src/glt/ir/LogEventDeserializer.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../ffi/ir_stream/decoding_methods.hpp" @@ -11,7 +12,7 @@ namespace glt::ir { template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { ffi::ir_stream::encoded_tag_t metadata_type{0}; std::vector metadata; auto ir_error_code = ffi::ir_stream::deserialize_preamble(reader, metadata_type, metadata); @@ -67,7 +68,7 @@ auto LogEventDeserializer::create(ReaderInterface& reader template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { epoch_time_ms_t timestamp_or_timestamp_delta{}; std::string logtype; std::vector dict_vars; @@ -106,11 +107,11 @@ auto LogEventDeserializer::deserialize_log_event( // Explicitly declare template specializations so that we can define the template methods in this // file template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; } // namespace glt::ir diff --git a/components/core/src/glt/ir/LogEventDeserializer.hpp b/components/core/src/glt/ir/LogEventDeserializer.hpp index 9667f889a..4fbfb3c53 100644 --- a/components/core/src/glt/ir/LogEventDeserializer.hpp +++ b/components/core/src/glt/ir/LogEventDeserializer.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "../ReaderInterface.hpp" #include "../TimestampPattern.hpp" @@ -34,7 +34,7 @@ class LogEventDeserializer { * or uses an unsupported version */ static auto create(ReaderInterface& reader - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; // Delete copy constructor and assignment LogEventDeserializer(LogEventDeserializer const&) = delete; @@ -59,7 +59,7 @@ class LogEventDeserializer { * - std::errc::result_out_of_range if the IR stream is corrupted */ [[nodiscard]] auto deserialize_log_event( - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; private: // Constructors diff --git a/components/core/src/glt/spdlog_with_specializations.hpp b/components/core/src/glt/spdlog_with_specializations.hpp index 8cd279e9e..1518d5a62 100644 --- a/components/core/src/glt/spdlog_with_specializations.hpp +++ b/components/core/src/glt/spdlog_with_specializations.hpp @@ -16,7 +16,7 @@ struct fmt::formatter { } template - auto format(glt::ErrorCode const& error_code, FormatContext& ctx) { + auto format(glt::ErrorCode const& error_code, FormatContext& ctx) const { return fmt::format_to(ctx.out(), "{}", static_cast(error_code)); } }; @@ -30,7 +30,8 @@ struct fmt::formatter> template auto - format(glt::ffi::search::ExactVariableToken const& v, FormatContext& ctx) { + format(glt::ffi::search::ExactVariableToken const& v, + FormatContext& ctx) const { return fmt::format_to( ctx.out(), "ExactVariableToken(\"{}\") as {}", @@ -48,7 +49,8 @@ struct fmt::formatter> { } template - auto format(glt::ffi::search::WildcardToken const& v, FormatContext& ctx) { + auto + format(glt::ffi::search::WildcardToken const& v, FormatContext& ctx) const { return fmt::format_to( ctx.out(), "WildcardToken(\"{}\") as {}TokenType({}){}", diff --git a/components/core/src/glt/streaming_archive/reader/Archive.hpp b/components/core/src/glt/streaming_archive/reader/Archive.hpp index 5588f692f..bdf35feaf 100644 --- a/components/core/src/glt/streaming_archive/reader/Archive.hpp +++ b/components/core/src/glt/streaming_archive/reader/Archive.hpp @@ -42,7 +42,7 @@ class Archive { * @param decompressed_msg * @param custom_arg Custom argument for the output function */ - typedef void (*OutputFunc)( + using OutputFunc = void (*)( std::string const& orig_file_path, streaming_archive::reader::Message const& compressed_msg, std::string const& decompressed_msg, diff --git a/components/core/src/reducer/CMakeLists.txt b/components/core/src/reducer/CMakeLists.txt index 16958da74..4685009f2 100644 --- a/components/core/src/reducer/CMakeLists.txt +++ b/components/core/src/reducer/CMakeLists.txt @@ -44,11 +44,16 @@ target_include_directories(reducer-server PRIVATE "${PROJECT_SOURCE_DIR}/submodu target_link_libraries(reducer-server PRIVATE Boost::program_options + Boost::system clp::string_utils fmt::fmt ${MONGOCXX_TARGET} msgpack-cxx spdlog::spdlog + # Threads::Threads is necessary because on Ubuntu Focal (20.04), libssl (a dependency of + # libmongocxx) depends on libpthread, but its cmake script doesn't seem to provide the flags + # to link against libpthread. + Threads::Threads ) # Put the built executable at the root of the build directory set_target_properties( diff --git a/components/core/src/reducer/CommandLineArguments.cpp b/components/core/src/reducer/CommandLineArguments.cpp index 2429fd9dc..fe9cf4a72 100644 --- a/components/core/src/reducer/CommandLineArguments.cpp +++ b/components/core/src/reducer/CommandLineArguments.cpp @@ -30,12 +30,12 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { "scheduler-host", po::value(&m_scheduler_host) ->default_value(m_scheduler_host), - "Host the search scheduler is running on" + "Host the query scheduler is running on" )( "scheduler-port", po::value(&m_scheduler_port) ->default_value(m_scheduler_port), - "Port the search scheduler is listening on" + "Port the query scheduler is listening on" )( "mongodb-uri", po::value(&m_mongodb_uri) diff --git a/components/core/src/reducer/ServerContext.cpp b/components/core/src/reducer/ServerContext.cpp index 8faa59c55..75b667da4 100644 --- a/components/core/src/reducer/ServerContext.cpp +++ b/components/core/src/reducer/ServerContext.cpp @@ -89,7 +89,7 @@ bool ServerContext::register_with_scheduler( try { boost::asio::connect(m_scheduler_socket, endpoints); } catch (boost::system::system_error& error) { - SPDLOG_ERROR("Failed to connect to search scheduler - {}", error.what()); + SPDLOG_ERROR("Failed to connect to query scheduler - {}", error.what()); return false; } @@ -120,12 +120,12 @@ bool ServerContext::register_with_scheduler( return true; } -bool ServerContext::ack_search_scheduler() { +bool ServerContext::ack_query_scheduler() { boost::system::error_code e; char const scheduler_response = 'y'; boost::asio::write(m_scheduler_socket, boost::asio::buffer(&scheduler_response, 1), e); if (e) { - SPDLOG_ERROR("Failed to notify search scheduler - {}", e.message()); + SPDLOG_ERROR("Failed to notify query scheduler - {}", e.message()); return false; } return true; @@ -253,7 +253,7 @@ bool ServerContext::try_finalize_results() { return false; } - // Notify the search scheduler that the results have been pushed - return ack_search_scheduler(); + // Notify the query scheduler that the results have been pushed + return ack_query_scheduler(); } } // namespace reducer diff --git a/components/core/src/reducer/ServerContext.hpp b/components/core/src/reducer/ServerContext.hpp index dc6fb1c4d..fbc2065ef 100644 --- a/components/core/src/reducer/ServerContext.hpp +++ b/components/core/src/reducer/ServerContext.hpp @@ -79,10 +79,10 @@ class ServerContext { bool register_with_scheduler(boost::asio::ip::tcp::resolver::results_type const& endpoints); /** - * Synchronously sends a generic acknowledgement to the search scheduler. + * Synchronously sends a generic acknowledgement to the query scheduler. * @return Whether the acknowledgement was sent successfully. */ - bool ack_search_scheduler(); + bool ack_query_scheduler(); /** * Increments the number of active receiver tasks which may receive some results. @@ -124,7 +124,7 @@ class ServerContext { /** * If all results have been received then this function tries to publish the pipeline's results - * to the results cache and notify the search scheduler. + * to the results cache and notify the query scheduler. * @return true if not all results have been received yet, or the results were published * successfully. * @return false otherwise. diff --git a/components/core/src/reducer/reducer_server.cpp b/components/core/src/reducer/reducer_server.cpp index 4c7bca348..ab35b7396 100644 --- a/components/core/src/reducer/reducer_server.cpp +++ b/components/core/src/reducer/reducer_server.cpp @@ -212,7 +212,7 @@ void SchedulerUpdateListenerTask::operator()( } // Synchronously notify the scheduler that the reducer is ready - if (false == m_server_ctx->ack_search_scheduler()) { + if (false == m_server_ctx->ack_query_scheduler()) { m_server_ctx->set_status(ServerStatus::RecoverableFailure); m_server_ctx->stop_event_loop(); return; diff --git a/components/core/submodules/boost-outcome b/components/core/submodules/boost-outcome deleted file mode 160000 index 39500a331..000000000 --- a/components/core/submodules/boost-outcome +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 39500a33117c23596673c1925479c7ff01b602f6 diff --git a/components/core/submodules/outcome b/components/core/submodules/outcome new file mode 160000 index 000000000..571f9c930 --- /dev/null +++ b/components/core/submodules/outcome @@ -0,0 +1 @@ +Subproject commit 571f9c930e672950e99d5d30f743603aaaf8014c diff --git a/components/core/tests/test-Array.cpp b/components/core/tests/test-Array.cpp new file mode 100644 index 000000000..20b68fbd0 --- /dev/null +++ b/components/core/tests/test-Array.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include + +#include "../src/clp/Array.hpp" + +using clp::Array; +using std::vector; + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEST_CASE("array_fundamental", "[clp::Array]") { + Array clp_array_empty{0}; + REQUIRE(clp_array_empty.empty()); + // NOLINTNEXTLINE(readability-container-size-empty) + REQUIRE((0 == clp_array_empty.size())); + REQUIRE((clp_array_empty.begin() == clp_array_empty.end())); + + constexpr size_t cBufferSize{1024}; + + vector std_vector; + for (int i{0}; i < cBufferSize; ++i) { + std_vector.push_back(i); + } + + Array clp_array{cBufferSize}; + auto const& clp_array_const_ref = clp_array; + std::for_each(clp_array_const_ref.begin(), clp_array_const_ref.end(), [](int i) -> void { + REQUIRE((0 == i)); + }); + + std::copy(std_vector.cbegin(), std_vector.cend(), clp_array.begin()); + + size_t idx{0}; + for (auto const val : clp_array) { + REQUIRE((val == clp_array.at(idx))); + REQUIRE((val == std_vector.at(idx))); + ++idx; + } + REQUIRE((cBufferSize == idx)); + REQUIRE_THROWS(clp_array.at(idx)); +} + +TEST_CASE("array_default_initializable", "[clp::Array]") { + Array clp_array_empty{0}; + REQUIRE(clp_array_empty.empty()); + // NOLINTNEXTLINE(readability-container-size-empty) + REQUIRE((0 == clp_array_empty.size())); + REQUIRE((clp_array_empty.begin() == clp_array_empty.end())); + + vector const std_vector{"yscope", "clp", "clp::Array", "default_initializable"}; + Array clp_array{std_vector.size()}; + std::copy(std_vector.cbegin(), std_vector.cend(), clp_array.begin()); + REQUIRE(std::equal(std_vector.begin(), std_vector.end(), clp_array.begin(), clp_array.end())); +} diff --git a/components/core/tests/test-BufferedFileReader.cpp b/components/core/tests/test-BufferedFileReader.cpp index 734eca87b..f182a777c 100644 --- a/components/core/tests/test-BufferedFileReader.cpp +++ b/components/core/tests/test-BufferedFileReader.cpp @@ -277,8 +277,7 @@ TEST_CASE("Test delimiter", "[BufferedFileReader]") { file_reader.open(test_file_path); std::string test_string; - clp::FileReader ref_file_reader; - ref_file_reader.open(test_file_path); + clp::FileReader ref_file_reader{test_file_path}; std::string ref_string; // Validate that a FileReader and a BufferedFileReader return the same strings (split by @@ -292,7 +291,6 @@ TEST_CASE("Test delimiter", "[BufferedFileReader]") { REQUIRE(test_string == ref_string); } - ref_file_reader.close(); file_reader.close(); boost::filesystem::remove(test_file_path); } diff --git a/components/core/tests/test-FileDescriptorReader.cpp b/components/core/tests/test-FileDescriptorReader.cpp new file mode 100644 index 000000000..eba592e85 --- /dev/null +++ b/components/core/tests/test-FileDescriptorReader.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/Array.hpp" +#include "../src/clp/FileDescriptorReader.hpp" +#include "../src/clp/FileReader.hpp" +#include "../src/clp/ReaderInterface.hpp" + +using clp::Array; + +namespace { +// Reused code starts +constexpr size_t cDefaultReaderBufferSize{1024}; + +[[nodiscard]] auto get_test_input_local_path() -> std::string; + +[[nodiscard]] auto get_test_input_path_relative_to_tests_dir() -> std::filesystem::path; + +/** + * @param reader + * @param read_buf_size The size of the buffer to use for individual reads from the reader. + * @return All data read from the given reader. + */ +auto get_content(clp::ReaderInterface& reader, size_t read_buf_size = cDefaultReaderBufferSize) + -> std::vector; + +auto get_test_input_local_path() -> std::string { + std::filesystem::path const current_file_path{__FILE__}; + auto const tests_dir{current_file_path.parent_path()}; + return (tests_dir / get_test_input_path_relative_to_tests_dir()).string(); +} + +auto get_test_input_path_relative_to_tests_dir() -> std::filesystem::path { + return std::filesystem::path{"test_log_files"} / "log.txt"; +} + +auto get_content(clp::ReaderInterface& reader, size_t read_buf_size) -> std::vector { + std::vector buf; + Array read_buf{read_buf_size}; + for (bool has_more_content{true}; has_more_content;) { + size_t num_bytes_read{}; + has_more_content = reader.read(read_buf.data(), read_buf_size, num_bytes_read); + std::string_view const view{read_buf.data(), num_bytes_read}; + buf.insert(buf.cend(), view.cbegin(), view.cend()); + } + return buf; +} +} // namespace + +// Reused code ends + +TEST_CASE("file_descriptor_reader_basic", "[FileDescriptorReader]") { + clp::FileReader ref_reader{get_test_input_local_path()}; + auto const expected{get_content(ref_reader)}; + + clp::FileDescriptorReader reader{get_test_input_local_path()}; + auto const actual{get_content(reader)}; + REQUIRE((actual == expected)); +} + +TEST_CASE("file_descriptor_reader_with_offset_and_seek", "[FileDescriptorReader]") { + constexpr size_t cOffset{319}; + + clp::FileReader ref_reader{get_test_input_local_path()}; + ref_reader.seek_from_begin(cOffset); + auto const expected{get_content(ref_reader)}; + auto const ref_end_pos{ref_reader.get_pos()}; + + clp::FileDescriptorReader reader(get_test_input_local_path()); + reader.seek_from_begin(cOffset); + auto const actual{get_content(reader)}; + auto const actual_end_pos{reader.get_pos()}; + + REQUIRE((actual_end_pos == ref_end_pos)); + REQUIRE((actual == expected)); +} diff --git a/components/core/tests/test-MemoryMappedFile.cpp b/components/core/tests/test-MemoryMappedFile.cpp new file mode 100644 index 000000000..9cb855e0b --- /dev/null +++ b/components/core/tests/test-MemoryMappedFile.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/FileReader.hpp" +#include "../src/clp/ReaderInterface.hpp" +#include "../src/clp/ReadOnlyMemoryMappedFile.hpp" + +namespace { +/** + * Reads all content from a reader. + * @param reader + * @return The content. + */ +[[nodiscard]] auto read_content(clp::ReaderInterface& reader) -> std::vector; + +[[nodiscard]] auto get_test_dir() -> std::filesystem::path; + +auto read_content(clp::ReaderInterface& reader) -> std::vector { + constexpr size_t cBufferSize{4096}; + std::array read_buf{}; + std::vector buf; + for (bool has_more_content{true}; has_more_content;) { + size_t num_bytes_read{}; + has_more_content = reader.read(read_buf.data(), read_buf.size(), num_bytes_read); + buf.insert(buf.cend(), read_buf.cbegin(), read_buf.cbegin() + num_bytes_read); + } + return buf; +} + +auto get_test_dir() -> std::filesystem::path { + std::filesystem::path const current_file_path{__FILE__}; + return current_file_path.parent_path(); +} +} // namespace + +TEST_CASE("memory_mapped_file_view_basic", "[ReadOnlyMemoryMappedFile]") { + auto const test_input_path{ + get_test_dir() / std::filesystem::path{"test_network_reader_src"} / "random.log" + }; + clp::FileReader file_reader{test_input_path.string()}; + auto const expected{read_content(file_reader)}; + + clp::ReadOnlyMemoryMappedFile const mmap_file{test_input_path.string()}; + auto const view{mmap_file.get_view()}; + REQUIRE((view.size() == expected.size())); + REQUIRE(std::equal(view.begin(), view.end(), expected.cbegin())); +} + +TEST_CASE("memory_mapped_file_view_empty", "[ReadOnlyMemoryMappedFile]") { + auto const test_input_path{ + get_test_dir() / std::filesystem::path{"test_schema_files"} / "empty_schema.txt" + }; + + clp::ReadOnlyMemoryMappedFile const mmap_file{test_input_path.string()}; + auto const view{mmap_file.get_view()}; + REQUIRE(view.empty()); +} diff --git a/components/core/tests/test-NetworkReader.cpp b/components/core/tests/test-NetworkReader.cpp index 958e31d8b..552775ea8 100644 --- a/components/core/tests/test-NetworkReader.cpp +++ b/components/core/tests/test-NetworkReader.cpp @@ -6,15 +6,21 @@ #include #include #include +#include #include #include #include +#include +#include +#include "../src/clp/Array.hpp" #include "../src/clp/CurlDownloadHandler.hpp" +#include "../src/clp/CurlGlobalInstance.hpp" #include "../src/clp/ErrorCode.hpp" #include "../src/clp/FileReader.hpp" #include "../src/clp/NetworkReader.hpp" +#include "../src/clp/Platform.hpp" #include "../src/clp/ReaderInterface.hpp" namespace { @@ -34,6 +40,16 @@ constexpr size_t cDefaultReaderBufferSize{1024}; auto get_content(clp::ReaderInterface& reader, size_t read_buf_size = cDefaultReaderBufferSize) -> std::vector; +/** + * Asserts whether the given `CURLcode` and the CURL return code stored in the given `NetworkReader` + * instance are the same, and prints a log message if not. + * @param expected + * @param reader + * @return Whether the the assertion succeeded. + */ +[[nodiscard]] auto +assert_curl_error_code(CURLcode expected, clp::NetworkReader const& reader) -> bool; + auto get_test_input_local_path() -> std::string { std::filesystem::path const current_file_path{__FILE__}; auto const tests_dir{current_file_path.parent_path()}; @@ -53,83 +69,87 @@ auto get_test_input_path_relative_to_tests_dir() -> std::filesystem::path { auto get_content(clp::ReaderInterface& reader, size_t read_buf_size) -> std::vector { std::vector buf; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - auto const read_buf{std::make_unique(read_buf_size)}; + clp::Array read_buf{read_buf_size}; for (bool has_more_content{true}; has_more_content;) { size_t num_bytes_read{}; - has_more_content = reader.read(read_buf.get(), read_buf_size, num_bytes_read); - std::string_view const view{read_buf.get(), num_bytes_read}; + has_more_content = reader.read(read_buf.data(), read_buf_size, num_bytes_read); + std::string_view const view{read_buf.data(), num_bytes_read}; buf.insert(buf.cend(), view.cbegin(), view.cend()); } return buf; } + +auto assert_curl_error_code(CURLcode expected, clp::NetworkReader const& reader) -> bool { + auto const ret_code{reader.get_curl_ret_code()}; + if (false == ret_code.has_value()) { + WARN("The CURL error code hasn't been set yet in the given reader."); + return false; + } + auto const actual{ret_code.value()}; + if (expected == actual) { + return true; + } + std::string message_to_log{ + "Unexpected CURL error code: " + std::to_string(actual) + + "; expected: " + std::to_string(expected) + }; + auto const curl_error_message{reader.get_curl_error_msg()}; + if (curl_error_message.has_value()) { + message_to_log += "\nError message:\n" + std::string{curl_error_message.value()}; + } + WARN(message_to_log); + return false; +} } // namespace TEST_CASE("network_reader_basic", "[NetworkReader]") { - clp::FileReader ref_reader; - ref_reader.open(get_test_input_local_path()); + clp::FileReader ref_reader{get_test_input_local_path()}; auto const expected{get_content(ref_reader)}; - ref_reader.close(); - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url()}; auto const actual{get_content(reader)}; - auto const ret_code{reader.get_curl_ret_code()}; - REQUIRE(ret_code.has_value()); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - REQUIRE((CURLE_OK == ret_code.value())); + REQUIRE(assert_curl_error_code(CURLE_OK, reader)); REQUIRE((actual == expected)); - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_with_offset_and_seek", "[NetworkReader]") { constexpr size_t cOffset{319}; - clp::FileReader ref_reader; - ref_reader.open(get_test_input_local_path()); + clp::FileReader ref_reader{get_test_input_local_path()}; ref_reader.seek_from_begin(cOffset); auto const expected{get_content(ref_reader)}; auto const ref_end_pos{ref_reader.get_pos()}; - ref_reader.close(); - - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); // Read from an offset onwards by starting the download from that offset. { + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url(), cOffset}; auto const actual{get_content(reader)}; - auto const ret_code{reader.get_curl_ret_code()}; - REQUIRE(ret_code.has_value()); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - REQUIRE((CURLE_OK == ret_code.value())); + REQUIRE(assert_curl_error_code(CURLE_OK, reader)); REQUIRE((reader.get_pos() == ref_end_pos)); REQUIRE((actual == expected)); } // Read from an offset onwards by seeking to that offset. { + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader(get_test_input_remote_url()); reader.seek_from_begin(cOffset); auto const actual{get_content(reader)}; - auto const ret_code{reader.get_curl_ret_code()}; - REQUIRE(ret_code.has_value()); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - REQUIRE((CURLE_OK == ret_code.value())); + REQUIRE(assert_curl_error_code(CURLE_OK, reader)); REQUIRE((reader.get_pos() == ref_end_pos)); REQUIRE((actual == expected)); } - - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_destruct", "[NetworkReader]") { - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); - // We sleep to fill out all the buffers, and then we delete the reader. The destructor will try // to abort the underlying download and then destroy the instance. So should ensure destructor // is successfully executed without deadlock or exceptions. bool no_exception{true}; try { // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + clp::CurlGlobalInstance const curl_global_instance; auto reader{std::make_unique( get_test_input_remote_url(), 0, @@ -147,25 +167,93 @@ TEST_CASE("network_reader_destruct", "[NetworkReader]") { no_exception = false; } REQUIRE(no_exception); - - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") { - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); - // Try to read from an out-of-bound offset. constexpr size_t cIllegalOffset{UINT32_MAX}; + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url(), cIllegalOffset}; - while (true) { - auto const ret_code{reader.get_curl_ret_code()}; - if (ret_code.has_value()) { - REQUIRE((CURLE_HTTP_RETURNED_ERROR == ret_code.value())); - size_t pos{}; - REQUIRE((clp::ErrorCode_Failure == reader.try_get_pos(pos))); + while (false == reader.get_curl_ret_code().has_value()) { + // Wait until the return code is ready + } + + if constexpr (clp::Platform::MacOs == clp::cCurrentPlatform) { + // On macOS, HTTP response code 416 is not handled as `CURL_HTTP_RETURNED_ERROR` in some + // `libcurl` versions. + REQUIRE( + (assert_curl_error_code(CURLE_HTTP_RETURNED_ERROR, reader) + || assert_curl_error_code(CURLE_RECV_ERROR, reader)) + ); + } else { + REQUIRE(assert_curl_error_code(CURLE_HTTP_RETURNED_ERROR, reader)); + } + size_t pos{}; + REQUIRE((clp::ErrorCode_Failure == reader.try_get_pos(pos))); +} + +TEST_CASE("network_reader_with_valid_http_header_kv_pairs", "[NetworkReader]") { + std::unordered_map valid_http_header_kv_pairs; + // We use httpbin (https://httpbin.org/) to test the user-specified headers. On success, it is + // supposed to respond all the user-specified headers as key-value pairs in JSON form. + constexpr size_t cNumHttpHeaderKeyValuePairs{10}; + for (size_t i{0}; i < cNumHttpHeaderKeyValuePairs; ++i) { + valid_http_header_kv_pairs.emplace( + fmt::format("Unit-Test-Key{}", i), + fmt::format("Unit-Test-Value{}", i) + ); + } + std::optional> optional_content; + // Retry the unit test a limited number of times to handle transient server-side HTTP errors. + // This ensures the test is not marked as failed due to temporary issues beyond our control. + constexpr size_t cNumMaxTrials{10}; + for (size_t i{0}; i < cNumMaxTrials; ++i) { + clp::NetworkReader reader{ + "https://httpbin.org/headers", + 0, + false, + clp::CurlDownloadHandler::cDefaultOverallTimeout, + clp::CurlDownloadHandler::cDefaultConnectionTimeout, + clp::NetworkReader::cDefaultBufferPoolSize, + clp::NetworkReader::cDefaultBufferSize, + valid_http_header_kv_pairs + }; + auto const content = get_content(reader); + if (assert_curl_error_code(CURLE_OK, reader)) { + optional_content.emplace(content); break; } } + REQUIRE(optional_content.has_value()); + auto const parsed_content = nlohmann::json::parse(optional_content.value()); + auto const& headers{parsed_content.at("headers")}; + for (auto const& [key, value] : valid_http_header_kv_pairs) { + REQUIRE((value == headers.at(key).get())); + } +} - clp::NetworkReader::deinit(); +TEST_CASE("network_reader_with_illegal_http_header_kv_pairs", "[NetworkReader]") { + auto illegal_header_kv_pairs = GENERATE( + // The following headers are determined by offset and disable_cache, which should not be + // overridden by user-defined headers. + std::unordered_map{{"Range", "bytes=100-"}}, + std::unordered_map{{"RAnGe", "bytes=100-"}}, + std::unordered_map{{"Cache-Control", "no-cache"}}, + std::unordered_map{{"Pragma", "no-cache"}}, + // The CRLF-terminated headers should be rejected. + std::unordered_map{{"Legal-Name", "CRLF\r\n"}} + ); + clp::NetworkReader reader{ + "https://httpbin.org/headers", + 0, + false, + clp::CurlDownloadHandler::cDefaultOverallTimeout, + clp::CurlDownloadHandler::cDefaultConnectionTimeout, + clp::NetworkReader::cDefaultBufferPoolSize, + clp::NetworkReader::cDefaultBufferSize, + illegal_header_kv_pairs + }; + auto const content = get_content(reader); + REQUIRE(content.empty()); + REQUIRE(assert_curl_error_code(CURLE_BAD_FUNCTION_ARGUMENT, reader)); } diff --git a/components/core/tests/test-ParserWithUserSchema.cpp b/components/core/tests/test-ParserWithUserSchema.cpp index d69a94958..3689c69e8 100644 --- a/components/core/tests/test-ParserWithUserSchema.cpp +++ b/components/core/tests/test-ParserWithUserSchema.cpp @@ -162,9 +162,8 @@ TEST_CASE("Test forward lexer", "[Search]") { std::string schema_file_name = "../tests/test_schema_files/search_schema.txt"; std::string schema_file_path = boost::filesystem::weakly_canonical(schema_file_name).string(); load_lexer_from_file(schema_file_path, false, forward_lexer); - FileReader file_reader; + FileReader file_reader{"../tests/test_search_queries/easy.txt"}; LogSurgeonReader reader_wrapper(file_reader); - file_reader.open("../tests/test_search_queries/easy.txt"); log_surgeon::ParserInputBuffer parser_input_buffer; parser_input_buffer.read_if_safe(reader_wrapper); forward_lexer.reset(); @@ -187,9 +186,8 @@ TEST_CASE("Test reverse lexer", "[Search]") { std::string schema_file_name = "../tests/test_schema_files/search_schema.txt"; std::string schema_file_path = boost::filesystem::weakly_canonical(schema_file_name).string(); load_lexer_from_file(schema_file_path, false, reverse_lexer); - FileReader file_reader; + FileReader file_reader{"../tests/test_search_queries/easy.txt"}; LogSurgeonReader reader_wrapper(file_reader); - file_reader.open("../tests/test_search_queries/easy.txt"); log_surgeon::ParserInputBuffer parser_input_buffer; parser_input_buffer.read_if_safe(reader_wrapper); reverse_lexer.reset(); diff --git a/components/core/tests/test-StreamingCompression.cpp b/components/core/tests/test-StreamingCompression.cpp index cad66d028..0fbae9e3a 100644 --- a/components/core/tests/test-StreamingCompression.cpp +++ b/components/core/tests/test-StreamingCompression.cpp @@ -1,240 +1,113 @@ +#include +#include +#include +#include +#include #include -#include +#include #include #include +#include "../src/clp/Array.hpp" +#include "../src/clp/ErrorCode.hpp" +#include "../src/clp/FileWriter.hpp" +#include "../src/clp/ReadOnlyMemoryMappedFile.hpp" +#include "../src/clp/streaming_compression/Compressor.hpp" +#include "../src/clp/streaming_compression/Decompressor.hpp" #include "../src/clp/streaming_compression/passthrough/Compressor.hpp" #include "../src/clp/streaming_compression/passthrough/Decompressor.hpp" #include "../src/clp/streaming_compression/zstd/Compressor.hpp" #include "../src/clp/streaming_compression/zstd/Decompressor.hpp" +using clp::Array; using clp::ErrorCode_Success; using clp::FileWriter; +using clp::streaming_compression::Compressor; +using clp::streaming_compression::Decompressor; TEST_CASE("StreamingCompression", "[StreamingCompression]") { - // Initialize data to test compression and decompression - size_t uncompressed_data_size = 128L * 1024 * 1024; // 128MB - char* uncompressed_data = new char[uncompressed_data_size]; - for (size_t i = 0; i < uncompressed_data_size; ++i) { - uncompressed_data[i] = (char)('a' + (i % 26)); + // Initialize constants + constexpr size_t cBufferSize{128L * 1024 * 1024}; // 128MB + constexpr auto cCompressionChunkSizes = std::to_array( + {cBufferSize / 100, + cBufferSize / 50, + cBufferSize / 25, + cBufferSize / 10, + cBufferSize / 5, + cBufferSize / 2, + cBufferSize} + ); + constexpr size_t cAlphabetLength{26}; + std::string const compressed_file_path{"test_streaming_compressed_file.bin"}; + + // Initialize compression devices + std::unique_ptr compressor; + std::unique_ptr decompressor; + + SECTION("ZStd single phase compression") { + compressor = std::make_unique(); + decompressor = std::make_unique(); } - // Create output buffer - char* decompressed_data = new char[uncompressed_data_size]; - - SECTION("zstd single phase compression") { - // Clear output buffer - memset(decompressed_data, 0, uncompressed_data_size); - std::string compressed_file_path = "compressed_file.zstd.bin.1"; - - // Compress - FileWriter file_writer; - file_writer.open(compressed_file_path, FileWriter::OpenMode::CREATE_FOR_WRITING); - clp::streaming_compression::zstd::Compressor compressor; - compressor.open(file_writer); - compressor.write(uncompressed_data, ZSTD_CStreamInSize()); - compressor.write(uncompressed_data, uncompressed_data_size / 100); - compressor.write(uncompressed_data, uncompressed_data_size / 50); - compressor.write(uncompressed_data, uncompressed_data_size / 25); - compressor.write(uncompressed_data, uncompressed_data_size / 10); - compressor.write(uncompressed_data, uncompressed_data_size / 5); - compressor.write(uncompressed_data, uncompressed_data_size / 2); - compressor.write(uncompressed_data, uncompressed_data_size); - compressor.close(); - file_writer.close(); - - // Decompress - clp::streaming_compression::zstd::Decompressor decompressor; - REQUIRE(ErrorCode_Success == decompressor.open(compressed_file_path)); - size_t uncompressed_bytes = 0; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - ZSTD_CStreamInSize() - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, ZSTD_CStreamInSize()) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += ZSTD_CStreamInSize(); - - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 100 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 100) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 100; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 50 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 50) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 50; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 25 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 25) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 25; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 10 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 10) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 10; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 5 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 5) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 5; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 2 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 2) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 2; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size; - - // Cleanup - boost::filesystem::remove(compressed_file_path); + SECTION("Passthrough compression") { + compressor = std::make_unique(); + decompressor = std::make_unique(); } - SECTION("passthrough compression") { - // Clear output buffer - memset(decompressed_data, 0, uncompressed_data_size); - std::string compressed_file_path = "compressed_file.passthrough.bin"; - - // Compress - FileWriter file_writer; - file_writer.open(compressed_file_path, FileWriter::OpenMode::CREATE_FOR_WRITING); - clp::streaming_compression::passthrough::Compressor compressor; - compressor.open(file_writer); - compressor.write(uncompressed_data, uncompressed_data_size / 100); - compressor.write(uncompressed_data, uncompressed_data_size / 50); - compressor.write(uncompressed_data, uncompressed_data_size / 25); - compressor.write(uncompressed_data, uncompressed_data_size / 10); - compressor.write(uncompressed_data, uncompressed_data_size / 5); - compressor.write(uncompressed_data, uncompressed_data_size / 2); - compressor.write(uncompressed_data, uncompressed_data_size); - compressor.close(); - file_writer.close(); - - // Decompress - // Memory map compressed file - // Create memory mapping for compressed_file_path, use boost read only memory mapped file - boost::system::error_code boost_error_code; - size_t compressed_file_size - = boost::filesystem::file_size(compressed_file_path, boost_error_code); - REQUIRE(!boost_error_code); - - boost::iostreams::mapped_file_params memory_map_params; - memory_map_params.path = compressed_file_path; - memory_map_params.flags = boost::iostreams::mapped_file::readonly; - memory_map_params.length = compressed_file_size; - boost::iostreams::mapped_file_source memory_mapped_compressed_file; - memory_mapped_compressed_file.open(memory_map_params); - REQUIRE(memory_mapped_compressed_file.is_open()); - - clp::streaming_compression::passthrough::Decompressor decompressor; - decompressor.open(memory_mapped_compressed_file.data(), compressed_file_size); + // Initialize buffers + Array uncompressed_buffer{cBufferSize}; + for (size_t i{0}; i < cBufferSize; ++i) { + uncompressed_buffer.at(i) = static_cast(('a' + (i % cAlphabetLength))); + } - size_t uncompressed_bytes = 0; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 100 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 100) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 100; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 50 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 50) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 50; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 25 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 25) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 25; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 10 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 10) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 10; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 5 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 5) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 5; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size / 2 - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size / 2) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size / 2; - REQUIRE(ErrorCode_Success - == decompressor.get_decompressed_stream_region( - uncompressed_bytes, - decompressed_data, - uncompressed_data_size - )); - REQUIRE(memcmp(uncompressed_data, decompressed_data, uncompressed_data_size) == 0); - memset(decompressed_data, 0, uncompressed_data_size); - uncompressed_bytes += uncompressed_data_size; + Array decompressed_buffer{cBufferSize}; - // Cleanup - boost::filesystem::remove(compressed_file_path); + // Compress + FileWriter file_writer; + file_writer.open(compressed_file_path, FileWriter::OpenMode::CREATE_FOR_WRITING); + compressor->open(file_writer); + for (auto const chunk_size : cCompressionChunkSizes) { + compressor->write(uncompressed_buffer.data(), chunk_size); + } + compressor->close(); + file_writer.close(); + + // Decompress and compare + clp::ReadOnlyMemoryMappedFile const memory_mapped_compressed_file{compressed_file_path}; + auto const compressed_file_view{memory_mapped_compressed_file.get_view()}; + decompressor->open(compressed_file_view.data(), compressed_file_view.size()); + + size_t num_uncompressed_bytes{0}; + for (auto const chunk_size : cCompressionChunkSizes) { + // Clear the buffer to ensure that we are not comparing values from a previous test + std::ranges::fill(decompressed_buffer.begin(), decompressed_buffer.end(), 0); + REQUIRE( + (ErrorCode_Success + == decompressor->get_decompressed_stream_region( + num_uncompressed_bytes, + decompressed_buffer.data(), + chunk_size + )) + ); + REQUIRE(std::equal( + uncompressed_buffer.begin(), + uncompressed_buffer.begin() + chunk_size, + decompressed_buffer.begin() + )); + num_uncompressed_bytes += chunk_size; } + // Sanity check + REQUIRE( + (std::accumulate( + cCompressionChunkSizes.cbegin(), + cCompressionChunkSizes.cend(), + size_t{0} + ) + == num_uncompressed_bytes) + ); + // Cleanup - delete[] uncompressed_data; - delete[] decompressed_data; + boost::filesystem::remove(compressed_file_path); } diff --git a/components/core/tests/test-clp_s-end_to_end.cpp b/components/core/tests/test-clp_s-end_to_end.cpp new file mode 100644 index 000000000..3f138b472 --- /dev/null +++ b/components/core/tests/test-clp_s-end_to_end.cpp @@ -0,0 +1,158 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "../src/clp_s/JsonConstructor.hpp" +#include "../src/clp_s/JsonParser.hpp" + +constexpr std::string_view cTestEndToEndArchiveDirectory{"test-end-to-end-archive"}; +constexpr std::string_view cTestEndToEndOutputDirectory{"test-end-to-end-out"}; +constexpr std::string_view cTestEndToEndOutputSortedJson{"test-end-to-end_sorted.jsonl"}; +constexpr std::string_view cTestEndToEndInputFileDirectory{"test_log_files"}; +constexpr std::string_view cTestEndToEndInputFile{"test_no_floats_sorted.jsonl"}; + +namespace { +/** + * A class that deletes the directories and files created by test cases, both before and after each + * test case where the class is instantiated. + */ +class TestOutputCleaner { +public: + TestOutputCleaner() { delete_files(); } + + ~TestOutputCleaner() { delete_files(); } + + // Delete copy & move constructors and assignment operators + TestOutputCleaner(TestOutputCleaner const&) = delete; + TestOutputCleaner(TestOutputCleaner&&) = delete; + auto operator=(TestOutputCleaner const&) -> TestOutputCleaner& = delete; + auto operator=(TestOutputCleaner&&) -> TestOutputCleaner& = delete; + +private: + static void delete_files() { + std::filesystem::remove_all(cTestEndToEndArchiveDirectory); + std::filesystem::remove_all(cTestEndToEndOutputDirectory); + std::filesystem::remove(cTestEndToEndOutputSortedJson); + } +}; + +auto get_test_input_path_relative_to_tests_dir() -> std::filesystem::path; +auto get_test_input_local_path() -> std::string; +void compress(bool structurize_arrays); +auto extract() -> std::filesystem::path; +void compare(std::filesystem::path const& extracted_json_path); + +auto get_test_input_path_relative_to_tests_dir() -> std::filesystem::path { + return std::filesystem::path{cTestEndToEndInputFileDirectory} / cTestEndToEndInputFile; +} + +auto get_test_input_local_path() -> std::string { + std::filesystem::path const current_file_path{__FILE__}; + auto const tests_dir{current_file_path.parent_path()}; + return (tests_dir / get_test_input_path_relative_to_tests_dir()).string(); +} + +void compress(bool structurize_arrays) { + constexpr auto cDefaultTargetEncodedSize = 8ULL * 1024 * 1024 * 1024; // 8 GiB + constexpr auto cDefaultMaxDocumentSize = 512ULL * 1024 * 1024; // 512 MiB + constexpr auto cDefaultMinTableSize = 1ULL * 1024 * 1024; // 1 MiB + constexpr auto cDefaultCompressionLevel = 3; + constexpr auto cDefaultPrintArchiveStats = false; + + std::filesystem::create_directory(cTestEndToEndArchiveDirectory); + REQUIRE((std::filesystem::is_directory(cTestEndToEndArchiveDirectory))); + + clp_s::JsonParserOption parser_option{}; + parser_option.file_paths.push_back(get_test_input_local_path()); + parser_option.archives_dir = cTestEndToEndArchiveDirectory; + parser_option.target_encoded_size = cDefaultTargetEncodedSize; + parser_option.max_document_size = cDefaultMaxDocumentSize; + parser_option.min_table_size = cDefaultMinTableSize; + parser_option.compression_level = cDefaultCompressionLevel; + parser_option.print_archive_stats = cDefaultPrintArchiveStats; + parser_option.structurize_arrays = structurize_arrays; + + clp_s::JsonParser parser{parser_option}; + REQUIRE(parser.parse()); + parser.store(); + + REQUIRE((false == std::filesystem::is_empty(cTestEndToEndArchiveDirectory))); +} + +auto extract() -> std::filesystem::path { + constexpr auto cDefaultOrdered = false; + constexpr auto cDefaultTargetOrderedChunkSize = 0; + + std::filesystem::create_directory(cTestEndToEndOutputDirectory); + REQUIRE(std::filesystem::is_directory(cTestEndToEndOutputDirectory)); + + clp_s::JsonConstructorOption constructor_option{}; + constructor_option.archives_dir = cTestEndToEndArchiveDirectory; + constructor_option.output_dir = cTestEndToEndOutputDirectory; + constructor_option.ordered = cDefaultOrdered; + constructor_option.target_ordered_chunk_size = cDefaultTargetOrderedChunkSize; + for (auto const& entry : std::filesystem::directory_iterator(constructor_option.archives_dir)) { + if (false == entry.is_directory()) { + // Skip non-directories + continue; + } + + constructor_option.archive_id = entry.path().filename(); + clp_s::JsonConstructor constructor{constructor_option}; + constructor.store(); + } + std::filesystem::path extracted_json_path{cTestEndToEndOutputDirectory}; + extracted_json_path /= "original"; + REQUIRE(std::filesystem::exists(extracted_json_path)); + + return extracted_json_path; +} + +// Silence the checks below since our use of `std::system` is safe in the context of testing. +// NOLINTBEGIN(cert-env33-c,concurrency-mt-unsafe) +void compare(std::filesystem::path const& extracted_json_path) { + int result{std::system("command -v jq >/dev/null 2>&1")}; + REQUIRE((0 == result)); + auto command = fmt::format( + "jq --sort-keys --compact-output '.' {} | sort > {}", + extracted_json_path.string(), + cTestEndToEndOutputSortedJson + ); + result = std::system(command.c_str()); + REQUIRE((0 == result)); + + REQUIRE((false == std::filesystem::is_empty(cTestEndToEndOutputSortedJson))); + + result = std::system("command -v diff >/dev/null 2>&1"); + REQUIRE((0 == result)); + command = fmt::format( + "diff --unified {} {} > /dev/null", + cTestEndToEndOutputSortedJson, + get_test_input_local_path() + ); + result = std::system(command.c_str()); + REQUIRE((true == WIFEXITED(result))); + REQUIRE((0 == WEXITSTATUS(result))); +} + +// NOLINTEND(cert-env33-c,concurrency-mt-unsafe) +} // namespace + +TEST_CASE("clp-s-compress-extract-no-floats", "[clp-s][end-to-end]") { + auto structurize_arrays = GENERATE(true, false); + + TestOutputCleaner const test_cleanup; + + compress(structurize_arrays); + + auto extracted_json_path = extract(); + + compare(extracted_json_path); +} diff --git a/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp b/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp new file mode 100644 index 000000000..5b8ad82cd --- /dev/null +++ b/components/core/tests/test-ffi_IrUnitHandlerInterface.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +#include + +#include "../src/clp/ffi/ir_stream/decoding_methods.hpp" +#include "../src/clp/ffi/ir_stream/IrUnitHandlerInterface.hpp" +#include "../src/clp/ffi/KeyValuePairLogEvent.hpp" +#include "../src/clp/ffi/SchemaTree.hpp" +#include "../src/clp/time_types.hpp" + +namespace { +using clp::ffi::ir_stream::IRErrorCode; +using clp::ffi::KeyValuePairLogEvent; +using clp::ffi::SchemaTree; +using clp::UtcOffset; + +constexpr UtcOffset cTestUtcOffset{100}; +constexpr UtcOffset cTestUtcOffsetDelta{1000}; +constexpr std::string_view cTestSchemaTreeNodeKeyName{"test_key"}; + +/** + * Class that implements `clp::ffi::ir_stream::IrUnitHandlerInterface` for testing purposes. + */ +class TrivialIrUnitHandler { +public: + // Implements `clp::ffi::ir_stream::IrUnitHandlerInterface` interface + [[nodiscard]] auto handle_log_event(KeyValuePairLogEvent&& log_event) -> IRErrorCode { + m_log_event.emplace(std::move(log_event)); + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] auto + handle_utc_offset_change(UtcOffset utc_offset_old, UtcOffset utc_offset_new) -> IRErrorCode { + m_utc_offset_delta = utc_offset_new - utc_offset_old; + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] auto handle_schema_tree_node_insertion( + SchemaTree::NodeLocator schema_tree_node_locator + ) -> IRErrorCode { + m_schema_tree_node_locator.emplace(schema_tree_node_locator); + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] auto handle_end_of_stream() -> IRErrorCode { + m_is_complete = true; + return IRErrorCode::IRErrorCode_Success; + } + + // Methods + [[nodiscard]] auto get_utc_offset_delta() const -> UtcOffset { return m_utc_offset_delta; } + + [[nodiscard]] auto is_complete() const -> bool { return m_is_complete; } + + [[nodiscard]] auto get_schema_tree_node_locator( + ) const -> std::optional const& { + return m_schema_tree_node_locator; + } + + [[nodiscard]] auto get_log_event() const -> std::optional const& { + return m_log_event; + } + +private: + UtcOffset m_utc_offset_delta{0}; + bool m_is_complete{false}; + std::optional m_schema_tree_node_locator; + std::optional m_log_event; +}; + +/** + * Class that inherits `TrivialIrUnitHandler` which also implements + * `clp::ffi::ir_stream::IrUnitHandlerInterface`. + */ +class TriviallyInheritedIrUnitHandler : public TrivialIrUnitHandler {}; + +/** + * Simulates the use of an IR unit handler. It calls every method required by + * `clp::ffi::ir_stream::IrUnitHandlerInterface` and ensure they don't return errors. + * @param handler + */ +auto test_ir_unit_handler_interface(clp::ffi::ir_stream::IrUnitHandlerInterface auto& handler +) -> void; + +auto test_ir_unit_handler_interface(clp::ffi::ir_stream::IrUnitHandlerInterface auto& handler +) -> void { + auto test_log_event_result{ + KeyValuePairLogEvent::create(std::make_shared(), {}, cTestUtcOffset) + }; + REQUIRE( + (false == test_log_event_result.has_error() + && IRErrorCode::IRErrorCode_Success + == handler.handle_log_event(std::move(test_log_event_result.value()))) + ); + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == handler.handle_utc_offset_change( + cTestUtcOffset, + cTestUtcOffset + cTestUtcOffsetDelta + )) + ); + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == handler.handle_schema_tree_node_insertion( + {SchemaTree::cRootId, cTestSchemaTreeNodeKeyName, SchemaTree::Node::Type::Obj} + )) + ); + REQUIRE((IRErrorCode::IRErrorCode_Success == handler.handle_end_of_stream())); +} +} // namespace + +TEMPLATE_TEST_CASE( + "test_ir_unit_handler_interface_basic", + "[ffi][ir_stream]", + TrivialIrUnitHandler, + TriviallyInheritedIrUnitHandler +) { + TestType handler; + REQUIRE_FALSE(handler.is_complete()); + test_ir_unit_handler_interface(handler); + + REQUIRE((handler.get_utc_offset_delta() == cTestUtcOffsetDelta)); + auto const& optional_log_event{handler.get_log_event()}; + REQUIRE( + (optional_log_event.has_value() + && optional_log_event.value().get_utc_offset() == cTestUtcOffset + && optional_log_event.value().get_node_id_value_pairs().empty()) + ); + auto const& optional_schema_tree_locator{handler.get_schema_tree_node_locator()}; + REQUIRE( + (optional_schema_tree_locator.has_value() + && optional_schema_tree_locator.value().get_key_name() == cTestSchemaTreeNodeKeyName) + ); + REQUIRE(handler.is_complete()); +} diff --git a/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp new file mode 100644 index 000000000..2e9cfb691 --- /dev/null +++ b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp @@ -0,0 +1,451 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/ffi/encoding_methods.hpp" +#include "../src/clp/ffi/KeyValuePairLogEvent.hpp" +#include "../src/clp/ffi/SchemaTree.hpp" +#include "../src/clp/ffi/Value.hpp" +#include "../src/clp/ir/EncodedTextAst.hpp" +#include "../src/clp/ir/types.hpp" +#include "../src/clp/time_types.hpp" + +using clp::ffi::KeyValuePairLogEvent; +using clp::ffi::SchemaTree; +using clp::ffi::Value; +using clp::ffi::value_bool_t; +using clp::ffi::value_float_t; +using clp::ffi::value_int_t; +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::EightByteEncodedTextAst; +using clp::ir::four_byte_encoded_variable_t; +using clp::ir::FourByteEncodedTextAst; +using clp::UtcOffset; +using std::string; +using std::vector; + +namespace { +constexpr std::string_view cStringToEncode{"uid=0, CPU usage: 99.99%, \"user_name\"=YScope"}; + +/** + * Parses and encodes the given string as an instance of `EncodedTextAst`. + * @tparam encoded_variable_t + * @param text + * @return The encoded result. + */ +template +requires(std::is_same_v + || std::is_same_v) +[[nodiscard]] auto get_encoded_text_ast(std::string_view text +) -> clp::ir::EncodedTextAst; + +/** + * Tests that `Value::is` returns true for the given type and false for all others. + * @tparam Type The type to query. + * @param value The value to test against. + */ +template +auto test_value_is(Value const& value) -> void; + +/** + * Tests `Value::get_immutable_view` either: + * 1. returns the expected value with the expected type for the given type and value; + * 2. throws for any other type. + * @tparam Type The type to query. + * @param value The value to test against. + * @param typed_value The typed value to compare with. + */ +template +auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void; + +/** + * Generates invalid node-ID value pairs with values that don't match the type of the schema tree + * node with the given ID. + * @param schema_tree + * @param node_id + * @param invalid_node_id_value_pairs Returns the pairs after insertion. + */ +auto insert_invalid_node_id_value_pairs_with_node_type_errors( + SchemaTree const& schema_tree, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& invalid_node_id_value_pairs +) -> void; + +template +requires(std::is_same_v + || std::is_same_v) +auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst { + string logtype; + vector encoded_vars; + vector dict_var_bounds; + REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds)); + REQUIRE(((dict_var_bounds.size() % 2) == 0)); + + vector dict_vars; + for (size_t i{0}; i < dict_var_bounds.size(); i += 2) { + auto const begin_pos{static_cast(dict_var_bounds[i])}; + auto const end_pos{static_cast(dict_var_bounds[i + 1])}; + dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos); + } + + return clp::ir::EncodedTextAst{logtype, dict_vars, encoded_vars}; +} + +template +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +auto test_value_is(Value const& value) -> void { + REQUIRE((std::is_same_v == value.is_null())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); +} + +template +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void { + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v< + EightByteEncodedTextAst const&, + decltype(value.get_immutable_view())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v< + FourByteEncodedTextAst const&, + decltype(value.get_immutable_view())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } +} + +auto insert_invalid_node_id_value_pairs_with_node_type_errors( + SchemaTree const& schema_tree, + SchemaTree::Node::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& invalid_node_id_value_pairs +) -> void { + REQUIRE((node_id < schema_tree.get_size())); + auto const node_type{schema_tree.get_node(node_id).get_type()}; + if (SchemaTree::Node::Type::Int != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(0)}); + } + if (SchemaTree::Node::Type::Float != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(0.0)}); + } + if (SchemaTree::Node::Type::Bool != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(false)}); + } + if (SchemaTree::Node::Type::Str != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast("Test")}); + if (SchemaTree::Node::Type::UnstructuredArray != node_type) { + invalid_node_id_value_pairs.emplace( + node_id, + Value{get_encoded_text_ast(cStringToEncode)} + ); + invalid_node_id_value_pairs.emplace( + node_id, + Value{get_encoded_text_ast(cStringToEncode)} + ); + } + } + if (SchemaTree::Node::Type::Obj != node_type) { + invalid_node_id_value_pairs.emplace(node_id, std::nullopt); + invalid_node_id_value_pairs.emplace(node_id, Value{}); + } +} +} // namespace + +TEST_CASE("ffi_Value_basic", "[ffi][Value]") { + Value const null_value; + test_value_is(null_value); + test_value_get_immutable_view(null_value, std::monostate{}); + + constexpr value_int_t cIntVal{1000}; + Value const int_value{cIntVal}; + test_value_is(int_value); + test_value_get_immutable_view(int_value, cIntVal); + + constexpr value_float_t cFloatValue{1000.0001}; + Value const float_value{cFloatValue}; + test_value_is(float_value); + test_value_get_immutable_view(float_value, cFloatValue); + + constexpr value_bool_t cBoolVal{false}; + Value const bool_value{cBoolVal}; + test_value_is(bool_value); + test_value_get_immutable_view(bool_value, cBoolVal); + + constexpr std::string_view cStringVal{"This is a test string message"}; + Value const string_value{string{cStringVal}}; + test_value_is(string_value); + test_value_get_immutable_view(string_value, string{cStringVal}); + + Value const eight_byte_encoded_text_ast_value{ + get_encoded_text_ast(cStringToEncode) + }; + test_value_is(eight_byte_encoded_text_ast_value); + test_value_get_immutable_view( + eight_byte_encoded_text_ast_value, + get_encoded_text_ast(cStringToEncode) + ); + + Value const four_byte_encoded_text_ast_value{ + get_encoded_text_ast(cStringToEncode) + }; + test_value_is(four_byte_encoded_text_ast_value); + test_value_get_immutable_view( + four_byte_encoded_text_ast_value, + get_encoded_text_ast(cStringToEncode) + ); +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { + /* + * <0:root:Obj> + * | + * |------------> <1:a:Obj> + * | | + * |--> <2:a:Int> |--> <3:b:Obj> + * | + * |------------> <4:c:Obj> + * | | + * |--> <5:d:Str> |--> <7:a:UnstructuredArray> + * | | + * |--> <6:d:Bool> |--> <8:d:Str> + * | | + * |--> <10:e:Obj> |--> <9:d:Float> + * | + * |--> <11:f:Obj> + */ + auto const schema_tree{std::make_shared()}; + std::vector const locators{ + {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj}, + {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int}, + {1, "b", SchemaTree::Node::Type::Obj}, + {3, "c", SchemaTree::Node::Type::Obj}, + {3, "d", SchemaTree::Node::Type::Str}, + {3, "d", SchemaTree::Node::Type::Bool}, + {4, "a", SchemaTree::Node::Type::UnstructuredArray}, + {4, "d", SchemaTree::Node::Type::Str}, + {4, "d", SchemaTree::Node::Type::Float}, + {3, "e", SchemaTree::Node::Type::Obj}, + {4, "f", SchemaTree::Node::Type::Obj} + }; + for (auto const& locator : locators) { + REQUIRE_NOTHROW(schema_tree->insert_node(locator)); + } + + SECTION("Test empty ID-value pairs") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + auto const result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pairs), + UtcOffset{0} + )}; + REQUIRE_FALSE(result.has_error()); + } + + SECTION("Test mismatched types") { + KeyValuePairLogEvent::NodeIdValuePairs invalid_node_id_value_pairs; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + // Int: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 2, + invalid_node_id_value_pairs + ); + + // Float: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 9, + invalid_node_id_value_pairs + ); + + // Bool: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 6, + invalid_node_id_value_pairs + ); + + // Str: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 5, + invalid_node_id_value_pairs + ); + + // UnstructuredArray: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 7, + invalid_node_id_value_pairs + ); + + // Obj: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 3, + invalid_node_id_value_pairs + ); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + for (auto const& [node_id, optional_value] : invalid_node_id_value_pairs) { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pair_to_test; + if (optional_value.has_value()) { + node_id_value_pair_to_test.emplace(node_id, optional_value.value()); + } else { + node_id_value_pair_to_test.emplace(node_id, std::nullopt); + } + auto const result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pair_to_test), + UtcOffset{0} + )}; + REQUIRE(result.has_error()); + auto const& err{result.error()}; + REQUIRE((std::errc::protocol_error == err)); + } + } + + SECTION("Test valid ID-value pairs") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + /* + * The sub schema tree of `node_id_value_pairs`: + * <0:root:Obj> + * | + * |------------> <1:a:Obj> + * | | + * |--> <2:a:Int> |--> <3:b:Obj> + * | + * |------------> <4:c:Obj> + * | | + * |--> <5:d:Str> |--> <7:a:UnstructuredArray> + * | | + * | |--> <8:d:Str> + * | | + * |--> <10:e:Obj> | + * | + * |--> <11:f:Obj> + */ + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(2, Value{static_cast(0)}); + node_id_value_pairs.emplace(5, Value{string{"Test"}}); + node_id_value_pairs.emplace( + 8, + Value{get_encoded_text_ast(cStringToEncode)} + ); + node_id_value_pairs.emplace( + 7, + Value{get_encoded_text_ast(cStringToEncode)} + ); + node_id_value_pairs.emplace(10, Value{}); + node_id_value_pairs.emplace(11, std::nullopt); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE_FALSE(result.has_error()); + + SECTION("Test duplicated key conflict on node #3") { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(6, Value{static_cast(false)}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE(result.has_error()); + REQUIRE((std::errc::protocol_not_supported == result.error())); + } + + SECTION("Test duplicated key conflict on node #4") { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(9, Value{static_cast(0.0)}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE(result.has_error()); + REQUIRE((std::errc::protocol_not_supported == result.error())); + } + + SECTION("Test invalid sub-tree on node #3") { + node_id_value_pairs.emplace(3, std::nullopt); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + // Node #3 is empty, but its descendants appear in the sub schema tree (node #5 & #10) + REQUIRE(result.has_error()); + REQUIRE((std::errc::operation_not_permitted == result.error())); + } + + SECTION("Test invalid sub-tree on node #4") { + node_id_value_pairs.emplace(4, Value{}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + // Node #4 is null, but its descendants appear in the sub schema tree (node #5 & #10) + REQUIRE(result.has_error()); + REQUIRE((std::errc::operation_not_permitted == result.error())); + } + } + + SECTION("Test out-of-bound node ID") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs_out_of_bound; + node_id_value_pairs_out_of_bound.emplace( + static_cast(schema_tree->get_size()), + Value{} + ); + auto const out_of_bound_result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pairs_out_of_bound), + UtcOffset{0} + )}; + REQUIRE(out_of_bound_result.has_error()); + REQUIRE((std::errc::operation_not_permitted == out_of_bound_result.error())); + } +} diff --git a/components/core/tests/test-ffi_SchemaTree.cpp b/components/core/tests/test-ffi_SchemaTree.cpp index bbae502e6..2808e03c0 100644 --- a/components/core/tests/test-ffi_SchemaTree.cpp +++ b/components/core/tests/test-ffi_SchemaTree.cpp @@ -1,14 +1,11 @@ -#include #include #include #include #include "../src/clp/ffi/SchemaTree.hpp" -#include "../src/clp/ffi/SchemaTreeNode.hpp" using clp::ffi::SchemaTree; -using clp::ffi::SchemaTreeNode; namespace { /** @@ -19,41 +16,63 @@ namespace { */ [[nodiscard]] auto insert_node( SchemaTree& schema_tree, - SchemaTree::NodeLocator locator, - SchemaTreeNode::id_t expected_id + SchemaTree::NodeLocator const& locator, + SchemaTree::Node::id_t expected_id ) -> bool; /** * @param schema_tree * @param locator * @param expected_id - * @return Whether the node exists and its ID matches the expected ID. + * @return Whether an ID could be found for a non root node matching the locator, the ID matches the + * expected ID, the corresponding node is not the root, and it matches the locator. */ -[[nodiscard]] auto check_node( +[[nodiscard]] auto check_non_root_node( SchemaTree const& schema_tree, - SchemaTree::NodeLocator locator, - SchemaTreeNode::id_t expected_id + SchemaTree::NodeLocator const& locator, + SchemaTree::Node::id_t expected_id ) -> bool; auto insert_node( SchemaTree& schema_tree, - SchemaTree::NodeLocator locator, - SchemaTreeNode::id_t expected_id + SchemaTree::NodeLocator const& locator, + SchemaTree::Node::id_t expected_id ) -> bool { return false == schema_tree.has_node(locator) && expected_id == schema_tree.insert_node(locator); } -auto check_node( +auto check_non_root_node( SchemaTree const& schema_tree, - SchemaTree::NodeLocator locator, - SchemaTreeNode::id_t expected_id + SchemaTree::NodeLocator const& locator, + SchemaTree::Node::id_t expected_id ) -> bool { auto const node_id{schema_tree.try_get_node_id(locator)}; - return node_id.has_value() && node_id.value() == expected_id; + if (false == node_id.has_value() || node_id.value() != expected_id) { + // The node's ID doesn't match. + return false; + } + auto const& node{schema_tree.get_node(expected_id)}; + if (node.is_root()) { + // Any nodes added after the tree was constructed must not be the root. + return false; + } + auto const optional_parent_id{node.get_parent_id()}; + if (false == optional_parent_id.has_value()) { + // Non-root nodes must have a parent ID. + return false; + } + if (optional_parent_id.value() != locator.get_parent_id() + || node.get_type() != locator.get_type() || node.get_key_name() != locator.get_key_name()) + { + // The node information doesn't match the locator. + return false; + } + return true; } } // namespace +// NOLINTNEXTLINE(readability-function-cognitive-complexity) TEST_CASE("ffi_schema_tree", "[ffi]") { /* * <0:root:Obj> @@ -69,39 +88,46 @@ TEST_CASE("ffi_schema_tree", "[ffi]") { * |--> <6:d:Bool> |--> <8:d:Str> */ SchemaTree schema_tree; + + // Check the root node + auto const& root{schema_tree.get_root()}; + REQUIRE((SchemaTree::cRootId == root.get_id())); + REQUIRE(root.is_root()); + REQUIRE_FALSE(root.get_parent_id().has_value()); + std::vector const locators{ - {SchemaTree::cRootId, "a", SchemaTreeNode::Type::Obj}, - {SchemaTree::cRootId, "a", SchemaTreeNode::Type::Int}, - {1, "b", SchemaTreeNode::Type::Obj}, - {3, "c", SchemaTreeNode::Type::Obj}, - {3, "d", SchemaTreeNode::Type::Int}, - {3, "d", SchemaTreeNode::Type::Bool}, - {4, "d", SchemaTreeNode::Type::UnstructuredArray}, - {4, "d", SchemaTreeNode::Type::Str} + {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj}, + {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int}, + {1, "b", SchemaTree::Node::Type::Obj}, + {3, "c", SchemaTree::Node::Type::Obj}, + {3, "d", SchemaTree::Node::Type::Int}, + {3, "d", SchemaTree::Node::Type::Bool}, + {4, "d", SchemaTree::Node::Type::UnstructuredArray}, + {4, "d", SchemaTree::Node::Type::Str} }; - auto const snapshot_idx{static_cast(locators.size() / 2)}; + auto const snapshot_idx{static_cast(locators.size() / 2)}; - for (SchemaTreeNode::id_t id_to_insert{1}; id_to_insert <= locators.size(); ++id_to_insert) { + for (SchemaTree::Node::id_t id_to_insert{1}; id_to_insert <= locators.size(); ++id_to_insert) { REQUIRE(insert_node(schema_tree, locators[id_to_insert - 1], id_to_insert)); if (snapshot_idx == id_to_insert) { schema_tree.take_snapshot(); } } - for (SchemaTreeNode::id_t id_to_check{1}; id_to_check <= locators.size(); ++id_to_check) { - REQUIRE(check_node(schema_tree, locators[id_to_check - 1], id_to_check)); + for (SchemaTree::Node::id_t id_to_check{1}; id_to_check <= locators.size(); ++id_to_check) { + REQUIRE(check_non_root_node(schema_tree, locators[id_to_check - 1], id_to_check)); } schema_tree.revert(); - for (SchemaTreeNode::id_t id_to_insert{snapshot_idx + 1}; id_to_insert <= locators.size(); + for (SchemaTree::Node::id_t id_to_insert{snapshot_idx + 1}; id_to_insert <= locators.size(); ++id_to_insert) { REQUIRE(insert_node(schema_tree, locators[id_to_insert - 1], id_to_insert)); } - for (SchemaTreeNode::id_t id_to_check{1}; id_to_check <= locators.size(); ++id_to_check) { - REQUIRE(check_node(schema_tree, locators[id_to_check - 1], id_to_check)); + for (SchemaTree::Node::id_t id_to_check{1}; id_to_check <= locators.size(); ++id_to_check) { + REQUIRE(check_non_root_node(schema_tree, locators[id_to_check - 1], id_to_check)); } } diff --git a/components/core/tests/test-hash_utils.cpp b/components/core/tests/test-hash_utils.cpp new file mode 100644 index 000000000..9e5aacd47 --- /dev/null +++ b/components/core/tests/test-hash_utils.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include + +#include "../src/clp/ErrorCode.hpp" +#include "../src/clp/hash_utils.hpp" +#include "../src/clp/type_utils.hpp" + +using clp::convert_to_hex_string; +using clp::ErrorCode_Success; +using clp::get_hmac_sha256_hash; +using clp::get_sha256_hash; +using clp::size_checked_pointer_cast; +using std::string_view; +using std::vector; + +TEST_CASE("test_sha256", "[hash_utils]") { + constexpr string_view cInputString{"ThisIsARandomTestInput"}; + constexpr string_view cReferenceSha256{ + "c3a1d9f04ada1198c4c63bf51d9933fc2cc216429275cadabdcb2178775add0c" + }; + vector hash; + + REQUIRE(ErrorCode_Success + == get_sha256_hash( + {size_checked_pointer_cast(cInputString.data()), + cInputString.size()}, + hash + )); + REQUIRE(convert_to_hex_string(hash) == cReferenceSha256); +} + +TEST_CASE("test_hmac", "[hash_utils]") { + constexpr string_view cInputString{"ThisIsARandomTestInput"}; + constexpr string_view cInputKey{"ThisIsATestKey"}; + constexpr string_view cReferenceHmacSha256{ + "38373057694c1038a6895212bea46849eb7a59b73a2ec175883ae095fb91ffda" + }; + vector hmac_hash; + + REQUIRE(ErrorCode_Success + == get_hmac_sha256_hash( + {size_checked_pointer_cast(cInputString.data()), + cInputString.size()}, + {size_checked_pointer_cast(cInputKey.data()), + cInputKey.size()}, + hmac_hash + )); + REQUIRE(convert_to_hex_string(hmac_hash) == cReferenceHmacSha256); +} diff --git a/components/core/tests/test-ir_encoding_methods.cpp b/components/core/tests/test-ir_encoding_methods.cpp index e56ea4e86..1ee1e3542 100644 --- a/components/core/tests/test-ir_encoding_methods.cpp +++ b/components/core/tests/test-ir_encoding_methods.cpp @@ -1,14 +1,29 @@ +#include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include #include "../src/clp/BufferReader.hpp" +#include "../src/clp/ErrorCode.hpp" #include "../src/clp/ffi/encoding_methods.hpp" #include "../src/clp/ffi/ir_stream/decoding_methods.hpp" +#include "../src/clp/ffi/ir_stream/Deserializer.hpp" #include "../src/clp/ffi/ir_stream/encoding_methods.hpp" +#include "../src/clp/ffi/ir_stream/IrUnitType.hpp" #include "../src/clp/ffi/ir_stream/protocol_constants.hpp" +#include "../src/clp/ffi/ir_stream/Serializer.hpp" +#include "../src/clp/ffi/ir_stream/utils.hpp" +#include "../src/clp/ffi/KeyValuePairLogEvent.hpp" +#include "../src/clp/ffi/SchemaTree.hpp" #include "../src/clp/ir/LogEventDeserializer.hpp" #include "../src/clp/ir/types.hpp" #include "../src/clp/time_types.hpp" @@ -26,11 +41,14 @@ using clp::ffi::ir_stream::cProtocol::MagicNumberLength; using clp::ffi::ir_stream::deserialize_preamble; using clp::ffi::ir_stream::deserialize_tag; using clp::ffi::ir_stream::deserialize_utc_offset_change; +using clp::ffi::ir_stream::Deserializer; using clp::ffi::ir_stream::encoded_tag_t; using clp::ffi::ir_stream::get_encoding_type; using clp::ffi::ir_stream::IRErrorCode; using clp::ffi::ir_stream::serialize_utc_offset_change; +using clp::ffi::ir_stream::Serializer; using clp::ffi::ir_stream::validate_protocol_version; +using clp::ffi::KeyValuePairLogEvent; using clp::ffi::wildcard_query_matches_any_encoded_var; using clp::ir::eight_byte_encoded_variable_t; using clp::ir::epoch_time_ms_t; @@ -71,6 +89,47 @@ class UnstructuredLogEvent { UtcOffset m_utc_offset{0}; }; +/** + * Class that implements `clp::ffi::ir_stream::IrUnitHandlerInterface` for testing purposes. + */ +class IrUnitHandler { +public: + // Implements `clp::ffi::ir_stream::IrUnitHandlerInterface` interface + [[nodiscard]] auto handle_log_event(KeyValuePairLogEvent&& log_event) -> IRErrorCode { + m_deserialized_log_events.emplace_back(std::move(log_event)); + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] static auto handle_utc_offset_change( + [[maybe_unused]] UtcOffset utc_offset_old, + [[maybe_unused]] UtcOffset utc_offset_new + ) -> IRErrorCode { + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] static auto handle_schema_tree_node_insertion( + [[maybe_unused]] clp::ffi::SchemaTree::NodeLocator schema_tree_node_locator + ) -> IRErrorCode { + return IRErrorCode::IRErrorCode_Success; + } + + [[nodiscard]] auto handle_end_of_stream() -> IRErrorCode { + m_is_complete = true; + return IRErrorCode::IRErrorCode_Success; + } + + // Methods + [[nodiscard]] auto is_complete() const -> bool { return m_is_complete; } + + [[nodiscard]] auto get_deserialized_log_events() const -> vector const& { + return m_deserialized_log_events; + } + +private: + vector m_deserialized_log_events; + bool m_is_complete{false}; +}; + /** * Serializes the given log events into an IR buffer. * @tparam encoded_variable_t Type of the encoded variables. @@ -115,6 +174,40 @@ template */ [[nodiscard]] auto get_current_ts() -> epoch_time_ms_t; +/** + * Flushes and clears serialized data from the serializer's underlying IR buffer into the given byte + * buffer. + * @tparam encoded_variable_t + * @param serializer + * @param byte_buf + */ +template +auto flush_and_clear_serializer_buffer( + Serializer& serializer, + std::vector& byte_buf +) -> void; + +/** + * Unpacks and serializes the given msgpack bytes using kv serializer. + * @tparam encoded_variable_t + * @param msgpack_bytes + * @param serializer + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto unpack_and_serialize_msgpack_bytes( + vector const& msgpack_bytes, + Serializer& serializer +) -> bool; + +/** + * Counts the number of leaves in a JSON tree. A node is considered as a leaf if it's a primitive + * value, an empty map (`{}`), or an array. + * @param root + * @return The number of leaves under the given root. + */ +[[nodiscard]] auto count_num_leaves(nlohmann::json const& root) -> size_t; + template [[nodiscard]] auto serialize_log_events( vector const& log_events, @@ -214,6 +307,56 @@ auto create_test_log_events() -> vector { auto get_current_ts() -> epoch_time_ms_t { return duration_cast(system_clock::now().time_since_epoch()).count(); } + +template +auto flush_and_clear_serializer_buffer( + Serializer& serializer, + vector& byte_buf +) -> void { + auto const view{serializer.get_ir_buf_view()}; + byte_buf.insert(byte_buf.cend(), view.begin(), view.end()); + serializer.clear_ir_buf(); +} + +template +auto unpack_and_serialize_msgpack_bytes( + vector const& msgpack_bytes, + Serializer& serializer +) -> bool { + auto const msgpack_obj_handle{msgpack::unpack( + clp::size_checked_pointer_cast(msgpack_bytes.data()), + msgpack_bytes.size() + )}; + auto const msgpack_obj{msgpack_obj_handle.get()}; + if (msgpack::type::MAP != msgpack_obj.type) { + return false; + } + return serializer.serialize_msgpack_map(msgpack_obj.via.map); +} + +// NOLINTNEXTLINE(misc-no-recursion) +auto count_num_leaves(nlohmann::json const& root) -> size_t { + if (false == root.is_object()) { + return 0; + } + + size_t num_leaves{0}; + for (auto const& [key, val] : root.items()) { + if (val.is_primitive() || val.is_array()) { + ++num_leaves; + } else if (val.is_object()) { + if (val.empty()) { + ++num_leaves; + } else { + num_leaves += count_num_leaves(val); + } + } else { + FAIL("Unknown JSON object types."); + } + } + + return num_leaves; +} } // namespace /** @@ -487,8 +630,8 @@ TEMPLATE_TEST_CASE( auto metadata_json = nlohmann::json::parse(json_metadata); std::string const version = metadata_json.at(clp::ffi::ir_stream::cProtocol::Metadata::VersionKey); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Supported == validate_protocol_version(version) - ); + REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode::BackwardCompatible + == validate_protocol_version(version)); REQUIRE(clp::ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type); set_timestamp_info(metadata_json, ts_info); REQUIRE(timestamp_pattern_syntax == ts_info.timestamp_pattern_syntax); @@ -701,17 +844,63 @@ TEST_CASE("decode_next_message_four_byte_timestamp_delta", "[ffi][deserialize_lo } TEST_CASE("validate_protocol_version", "[ffi][validate_version_protocol]") { - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("v0.0.1") + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::Supported + == validate_protocol_version(clp::ffi::ir_stream::cProtocol::Metadata::VersionValue)) + ); + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::BackwardCompatible + == validate_protocol_version( + clp::ffi::ir_stream::cProtocol::Metadata::LatestBackwardCompatibleVersion + )) ); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("0.1")); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Invalid == validate_protocol_version("0.a.1")); - - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Too_New - == validate_protocol_version("1000.0.0")); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Supported - == validate_protocol_version(clp::ffi::ir_stream::cProtocol::Metadata::VersionValue)); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Supported - == validate_protocol_version("v0.0.0")); + + SECTION("Test invalid versions") { + auto const invalid_versions{GENERATE( + std::string_view{"v0.0.1"}, + std::string_view{"0.1"}, + std::string_view{"0.1.a"}, + std::string_view{"0.a.1"} + )}; + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::Invalid + == validate_protocol_version(invalid_versions)) + ); + } + + SECTION("Test backward compatible versions") { + auto const backward_compatible_versions{GENERATE( + std::string_view{"v0.0.0"}, + std::string_view{"0.0.1"}, + std::string_view{"0.0.2"} + )}; + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::BackwardCompatible + == validate_protocol_version(backward_compatible_versions)) + ); + } + + SECTION("Test versions that're too old") { + auto const old_versions{GENERATE( + std::string_view{"0.0.3"}, + std::string_view{"0.0.3-beta.1"}, + std::string_view{"0.1.0-beta"} + )}; + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::Unsupported + == validate_protocol_version(old_versions)) + ); + } + + SECTION("Test versions that're too new") { + auto const new_versions{ + GENERATE(std::string_view{"10000.0.0"}, std::string_view{"0.10000.0"}) + }; + REQUIRE( + (clp::ffi::ir_stream::IRProtocolErrorCode::Unsupported + == validate_protocol_version(new_versions)) + ); + } } TEMPLATE_TEST_CASE( @@ -762,8 +951,8 @@ TEMPLATE_TEST_CASE( string_view json_metadata{json_metadata_ptr, metadata_size}; auto metadata_json = nlohmann::json::parse(json_metadata); string const version = metadata_json.at(clp::ffi::ir_stream::cProtocol::Metadata::VersionKey); - REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode_Supported == validate_protocol_version(version) - ); + REQUIRE(clp::ffi::ir_stream::IRProtocolErrorCode::BackwardCompatible + == validate_protocol_version(version)); REQUIRE(clp::ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type); set_timestamp_info(metadata_json, ts_info); REQUIRE(timestamp_pattern_syntax == ts_info.timestamp_pattern_syntax); @@ -851,10 +1040,293 @@ TEMPLATE_TEST_CASE( REQUIRE(log_event.get_utc_offset() == ref_log_event.get_utc_offset()); // We only compare the logtype since decoding messages from logtype + variables is not yet // supported by our public interfaces - REQUIRE(log_event.get_logtype() == encoded_logtypes.at(log_event_idx)); + REQUIRE(log_event.get_message().get_logtype() == encoded_logtypes.at(log_event_idx)); ++log_event_idx; } auto result = log_event_deserializer.deserialize_log_event(); REQUIRE(result.has_error()); REQUIRE(std::errc::no_message_available == result.error()); } + +TEMPLATE_TEST_CASE( + "ffi_ir_stream_Serializer_creation", + "[clp][ffi][ir_stream][Serializer]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + // This is a unit test for the kv-pair IR serializer. Currently, we haven't yet implemented a + // deserializer, so we can only test whether the preamble packet is serialized correctly. + vector ir_buf; + + auto result{Serializer::create()}; + REQUIRE((false == result.has_error())); + + auto& serializer{result.value()}; + flush_and_clear_serializer_buffer(serializer, ir_buf); + REQUIRE(serializer.get_ir_buf_view().empty()); + + constexpr UtcOffset cBeijingUtcOffset{8 * 60 * 60 * 1000}; + serializer.change_utc_offset(cBeijingUtcOffset); + flush_and_clear_serializer_buffer(serializer, ir_buf); + REQUIRE(serializer.get_ir_buf_view().empty()); + + ir_buf.push_back(clp::ffi::ir_stream::cProtocol::Eof); + + BufferReader buffer_reader{size_checked_pointer_cast(ir_buf.data()), ir_buf.size()}; + + bool is_four_byte_encoding{}; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == get_encoding_type(buffer_reader, is_four_byte_encoding)) + ); + if constexpr (std::is_same_v) { + REQUIRE(is_four_byte_encoding); + } else { + REQUIRE((false == is_four_byte_encoding)); + } + + encoded_tag_t metadata_type{}; + vector metadata_bytes; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == deserialize_preamble(buffer_reader, metadata_type, metadata_bytes)) + ); + REQUIRE((clp::ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type)); + string_view const metadata_view{ + size_checked_pointer_cast(metadata_bytes.data()), + metadata_bytes.size() + }; + nlohmann::json const metadata = nlohmann::json::parse(metadata_view); + + nlohmann::json expected_metadata; + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VersionKey, + clp::ffi::ir_stream::cProtocol::Metadata::VersionValue + ); + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VariablesSchemaIdKey, + clp::ffi::cVariablesSchemaVersion + ); + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VariableEncodingMethodsIdKey, + clp::ffi::cVariableEncodingMethodsVersion + ); + REQUIRE((expected_metadata == metadata)); + + encoded_tag_t encoded_tag{}; + REQUIRE((IRErrorCode::IRErrorCode_Success == deserialize_tag(buffer_reader, encoded_tag))); + REQUIRE((clp::ffi::ir_stream::cProtocol::Payload::UtcOffsetChange == encoded_tag)); + UtcOffset utc_offset_change{0}; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == deserialize_utc_offset_change(buffer_reader, utc_offset_change)) + ); + REQUIRE((cBeijingUtcOffset == utc_offset_change)); + + REQUIRE((IRErrorCode::IRErrorCode_Success == deserialize_tag(buffer_reader, encoded_tag))); + REQUIRE((clp::ffi::ir_stream::cProtocol::Eof == encoded_tag)); + + char eof{}; + size_t num_bytes_read{}; + REQUIRE( + (clp::ErrorCode_EndOfFile == buffer_reader.try_read(&eof, 1, num_bytes_read) + && 0 == num_bytes_read) + ); +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEMPLATE_TEST_CASE( + "ffi_ir_stream_Serializer_serialize_msgpack", + "[clp][ffi][ir_stream][Serializer]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + vector ir_buf; + vector serialized_json_objects; + + auto result{Serializer::create()}; + REQUIRE((false == result.has_error())); + + auto& serializer{result.value()}; + flush_and_clear_serializer_buffer(serializer, ir_buf); + + auto const empty_obj = nlohmann::json::parse("{}"); + REQUIRE(unpack_and_serialize_msgpack_bytes(nlohmann::json::to_msgpack(empty_obj), serializer)); + serialized_json_objects.emplace_back(empty_obj); + + // Test encoding basic object + constexpr string_view cShortString{"short_string"}; + constexpr string_view cClpString{"uid=0, CPU usage: 99.99%, \"user_name\"=YScope"}; + auto const empty_array = nlohmann::json::parse("[]"); + nlohmann::json const basic_obj + = {{"int8_max", INT8_MAX}, + {"int8_min", INT8_MIN}, + {"int16_max", INT16_MAX}, + {"int16_min", INT16_MIN}, + {"int32_max", INT32_MAX}, + {"int32_min", INT32_MIN}, + {"int64_max", INT64_MAX}, + {"int64_min", INT64_MIN}, + {"float_zero", 0.0}, + {"float_pos", 1.01}, + {"float_neg", -1.01}, + {"true", true}, + {"false", false}, + {"string", cShortString}, + {"clp_string", cClpString}, + {"null", nullptr}, + {"empty_object", empty_obj}, + {"empty_array", empty_array}}; + + REQUIRE(unpack_and_serialize_msgpack_bytes(nlohmann::json::to_msgpack(basic_obj), serializer)); + serialized_json_objects.emplace_back(basic_obj); + + auto basic_array = empty_array; + basic_array.emplace_back(1); + basic_array.emplace_back(1.0); + basic_array.emplace_back(true); + basic_array.emplace_back(cShortString); + basic_array.emplace_back(cClpString); + basic_array.emplace_back(nullptr); + basic_array.emplace_back(empty_array); + for (auto const& element : basic_array) { + // Non-map objects should not be serializable + REQUIRE( + (false + == unpack_and_serialize_msgpack_bytes( + nlohmann::json::to_msgpack(element), + serializer + )) + ); + } + basic_array.emplace_back(empty_obj); + + // Recursively construct an object containing inner maps and inner arrays. + auto recursive_obj = basic_obj; + auto recursive_array = basic_array; + constexpr size_t cRecursiveDepth{6}; + for (size_t i{0}; i < cRecursiveDepth; ++i) { + recursive_array.emplace_back(recursive_obj); + recursive_obj.emplace("obj_" + std::to_string(i), recursive_obj); + recursive_obj.emplace("array_" + std::to_string(i), recursive_array); + REQUIRE(unpack_and_serialize_msgpack_bytes( + nlohmann::json::to_msgpack(recursive_obj), + serializer + )); + serialized_json_objects.emplace_back(recursive_obj); + } + + flush_and_clear_serializer_buffer(serializer, ir_buf); + ir_buf.push_back(clp::ffi::ir_stream::cProtocol::Eof); + + // Deserialize the results + BufferReader reader{size_checked_pointer_cast(ir_buf.data()), ir_buf.size()}; + auto deserializer_result{Deserializer::create(reader, IrUnitHandler{})}; + REQUIRE_FALSE(deserializer_result.has_error()); + auto& deserializer = deserializer_result.value(); + while (true) { + auto const result{deserializer.deserialize_next_ir_unit(reader)}; + REQUIRE_FALSE(result.has_error()); + if (result.value() == clp::ffi::ir_stream::IrUnitType::EndOfStream) { + break; + } + } + auto const& ir_unit_handler{deserializer.get_ir_unit_handler()}; + + // Check the stream is complete + REQUIRE(ir_unit_handler.is_complete()); + REQUIRE(deserializer.is_stream_completed()); + // Check the number of log events deserialized matches the number of log events serialized + auto const& deserialized_log_events{ir_unit_handler.get_deserialized_log_events()}; + REQUIRE((serialized_json_objects.size() == deserialized_log_events.size())); + + auto const num_log_events{serialized_json_objects.size()}; + for (size_t idx{0}; idx < num_log_events; ++idx) { + auto const& expect{serialized_json_objects.at(idx)}; + auto const& deserialized_log_event{deserialized_log_events.at(idx)}; + + auto const num_leaves_in_json_obj{count_num_leaves(expect)}; + auto const num_kv_pairs{deserialized_log_event.get_node_id_value_pairs().size()}; + REQUIRE((num_leaves_in_json_obj == num_kv_pairs)); + + auto const serialized_json_result{deserialized_log_event.serialize_to_json()}; + REQUIRE_FALSE(serialized_json_result.has_error()); + REQUIRE((expect == serialized_json_result.value())); + } + + auto const eof_result{deserializer.deserialize_next_ir_unit(reader)}; + REQUIRE((eof_result.has_error() && std::errc::operation_not_permitted == eof_result.error())); +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEMPLATE_TEST_CASE( + "ffi_ir_stream_serialize_schema_tree_node_id", + "[clp][ffi][ir_stream]", + std::true_type, + std::false_type +) { + constexpr bool cIsAutoGeneratedNode{TestType{}}; + constexpr int8_t cOneByteLengthIndicatorTag{ + clp::ffi::ir_stream::cProtocol::Payload::EncodedSchemaTreeNodeIdByte + }; + constexpr int8_t cTwoByteLengthIndicatorTag{ + clp::ffi::ir_stream::cProtocol::Payload::EncodedSchemaTreeNodeIdShort + }; + constexpr int8_t cFourByteLengthIndicatorTag{ + clp::ffi::ir_stream::cProtocol::Payload::EncodedSchemaTreeNodeIdInt + }; + constexpr auto cMaxNodeId{static_cast(INT32_MAX)}; + + constexpr auto cSerializationMethodToTest + = clp::ffi::ir_stream::encode_and_serialize_schema_tree_node_id< + cIsAutoGeneratedNode, + cOneByteLengthIndicatorTag, + cTwoByteLengthIndicatorTag, + cFourByteLengthIndicatorTag>; + constexpr auto cDeserializationMethodToTest + = clp::ffi::ir_stream::deserialize_and_decode_schema_tree_node_id< + cOneByteLengthIndicatorTag, + cTwoByteLengthIndicatorTag, + cFourByteLengthIndicatorTag>; + + std::vector output_buf; + std::unordered_set valid_node_ids_to_test; + + // Add some boundary node IDs + valid_node_ids_to_test.emplace(0); + valid_node_ids_to_test.emplace(static_cast(INT8_MAX - 1)); + valid_node_ids_to_test.emplace(static_cast(INT8_MAX)); + valid_node_ids_to_test.emplace(static_cast(INT8_MAX + 1)); + valid_node_ids_to_test.emplace(static_cast(INT16_MAX - 1)); + valid_node_ids_to_test.emplace(static_cast(INT16_MAX)); + valid_node_ids_to_test.emplace(static_cast(INT16_MAX + 1)); + valid_node_ids_to_test.emplace(static_cast(INT32_MAX - 1)); + valid_node_ids_to_test.emplace(static_cast(INT32_MAX)); + + // Generate some more "random" valid node IDs + for (clp::ffi::SchemaTree::Node::id_t node_id{1}, step{1}; node_id <= cMaxNodeId; + node_id += step, step += 1) + { + valid_node_ids_to_test.emplace(node_id); + } + + for (auto const node_id : valid_node_ids_to_test) { + output_buf.clear(); + REQUIRE(cSerializationMethodToTest(node_id, output_buf)); + + BufferReader reader{size_checked_pointer_cast(output_buf.data()), output_buf.size()}; + encoded_tag_t tag{}; + REQUIRE((IRErrorCode::IRErrorCode_Success == deserialize_tag(reader, tag))); + auto const result{cDeserializationMethodToTest(tag, reader)}; + REQUIRE_FALSE(result.has_error()); + auto const [is_auto_generated, deserialized_node_id]{result.value()}; + REQUIRE((cIsAutoGeneratedNode == is_auto_generated)); + REQUIRE((deserialized_node_id == node_id)); + } + + // Test against the first invalid node ID + REQUIRE_FALSE(cSerializationMethodToTest( + static_cast(INT32_MAX) + 1, + output_buf + )); +} diff --git a/components/core/tests/test-ir_serializer.cpp b/components/core/tests/test-ir_serializer.cpp new file mode 100644 index 000000000..488f91d36 --- /dev/null +++ b/components/core/tests/test-ir_serializer.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/ffi/ir_stream/decoding_methods.hpp" +#include "../src/clp/ir/constants.hpp" +#include "../src/clp/ir/LogEventDeserializer.hpp" +#include "../src/clp/ir/LogEventSerializer.hpp" +#include "../src/clp/ir/types.hpp" +#include "../src/clp/streaming_compression/zstd/Decompressor.hpp" + +using clp::ffi::ir_stream::IRErrorCode::IRErrorCode_Success; +using clp::ir::cIrFileExtension; +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::epoch_time_ms_t; +using clp::ir::four_byte_encoded_variable_t; +using clp::ir::LogEventDeserializer; +using clp::ir::LogEventSerializer; +using clp::streaming_compression::zstd::Decompressor; +using std::chrono::milliseconds; +using std::chrono::system_clock; +using std::is_same_v; +using std::string; +using std::vector; + +namespace { +struct TestLogEvent { + epoch_time_ms_t timestamp; + string msg; +}; +} // namespace + +TEMPLATE_TEST_CASE( + "Encode and serialize log events", + "[ir][serialize-log-event]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + vector test_log_events; + + auto const ts_1 = duration_cast(system_clock::now().time_since_epoch()).count(); + vector const log_event_1_tokens + = {"Here is the first string with a small int ", + "4938", + " and a medium int ", + std::to_string(INT32_MAX), + " and a very large int ", + std::to_string(INT64_MAX), + " and a small float ", + "0.1", + "\n"}; + auto const log_event_1 + = std::accumulate(log_event_1_tokens.begin(), log_event_1_tokens.end(), string("")); + test_log_events.push_back({ts_1, log_event_1}); + + auto const ts_2 = duration_cast(system_clock::now().time_since_epoch()).count(); + vector const log_event_2_tokens + = {"Here is the second string with a medium float ", + "-25.519686", + " and a high precision float ", + "-25.5196868642755", + " and a weird float ", + "-00.00", + " and a string with numbers ", + "bin/python2.7.3", + " and another string with numbers ", + "abc123", + "\n"}; + auto const log_event_2 + = std::accumulate(log_event_2_tokens.begin(), log_event_2_tokens.end(), string("")); + test_log_events.push_back({ts_2, log_event_2}); + + string ir_test_file = "ir_serializer_test"; + ir_test_file += cIrFileExtension; + + LogEventSerializer serializer; + REQUIRE(serializer.open(ir_test_file)); + // Test serializing log events + for (auto const& test_log_event : test_log_events) { + REQUIRE(serializer.serialize_log_event(test_log_event.timestamp, test_log_event.msg)); + } + serializer.close(); + + Decompressor ir_reader; + ir_reader.open(ir_test_file); + + bool uses_four_byte_encoding{false}; + REQUIRE( + (IRErrorCode_Success + == clp::ffi::ir_stream::get_encoding_type(ir_reader, uses_four_byte_encoding)) + ); + REQUIRE((is_same_v == uses_four_byte_encoding)); + + auto result = LogEventDeserializer::create(ir_reader); + REQUIRE((false == result.has_error())); + auto& deserializer = result.value(); + + // Decode and deserialize all expected log events + for (auto const& test_log_event : test_log_events) { + auto deserialized_result = deserializer.deserialize_log_event(); + REQUIRE((false == deserialized_result.has_error())); + + auto& log_event = deserialized_result.value(); + auto const decoded_message = log_event.get_message().decode_and_unparse(); + REQUIRE(decoded_message.has_value()); + + REQUIRE((decoded_message.value() == test_log_event.msg)); + REQUIRE((log_event.get_timestamp() == test_log_event.timestamp)); + } + // Try decoding a nonexistent log event + auto deserialized_result = deserializer.deserialize_log_event(); + REQUIRE(deserialized_result.has_error()); + + std::filesystem::remove(ir_test_file); +} diff --git a/components/core/tests/test-kql.cpp b/components/core/tests/test-kql.cpp index 6b9eb594f..2646ff5e0 100644 --- a/components/core/tests/test-kql.cpp +++ b/components/core/tests/test-kql.cpp @@ -187,4 +187,49 @@ TEST_CASE("Test parsing KQL", "[KQL]") { auto failure = parse_kql_expression(incorrect_query); REQUIRE(nullptr == failure); } + + SECTION("Escape sequences in column name") { + auto query = GENERATE( + "a\\.b.c: *", + "\"a\\.b.c\": *", + "a\\.b: {c: *}", + "\"a\\.b\": {\"c\": *}" + ); + stringstream escaped_column_query{query}; + auto filter + = std::dynamic_pointer_cast(parse_kql_expression(escaped_column_query)); + REQUIRE(nullptr != filter); + REQUIRE(nullptr != filter->get_operand()); + REQUIRE(nullptr != filter->get_column()); + REQUIRE(false == filter->has_only_expression_operands()); + REQUIRE(false == filter->is_inverted()); + REQUIRE(FilterOperation::EQ == filter->get_operation()); + REQUIRE(2 == filter->get_column()->get_descriptor_list().size()); + auto it = filter->get_column()->descriptor_begin(); + REQUIRE(DescriptorToken{"a.b"} == *it++); + REQUIRE(DescriptorToken{"c"} == *it++); + } + + SECTION("Illegal escape sequences in column name") { + auto query = GENERATE( + //"a\\:*", this case is technically legal since ':' gets escaped + "\"a\\\":*", + "a\\ :*", + "\"a\\\" :*", + "a.:*", + "\"a.\":*", + "a. :*", + "\"a.\" :*" + ); + stringstream illegal_escape{query}; + auto filter = parse_kql_expression(illegal_escape); + REQUIRE(nullptr == filter); + } + + SECTION("Empty token in column name") { + auto query = GENERATE(".a:*", "a.:*", "a..c:*", "a.b.:*"); + stringstream empty_token_column{query}; + auto filter = parse_kql_expression(empty_token_column); + REQUIRE(nullptr == filter); + } } diff --git a/components/core/tests/test-query_methods.cpp b/components/core/tests/test-query_methods.cpp index d4555ac0b..b9b54a35e 100644 --- a/components/core/tests/test-query_methods.cpp +++ b/components/core/tests/test-query_methods.cpp @@ -165,6 +165,7 @@ TEMPLATE_TEST_CASE( message += " and a very large int " + var_strs[var_ix++]; message += " and a small double " + var_strs[var_ix++]; message += " and a medium double " + var_strs[var_ix++]; + message += " and a high precison double " + var_strs[var_ix++]; message += " and a weird double " + var_strs[var_ix++]; message += " and a string with numbers " + var_strs[var_ix++]; message += " and another string with numbers " + var_strs[var_ix++]; diff --git a/components/core/tests/test-regex_utils.cpp b/components/core/tests/test-regex_utils.cpp new file mode 100644 index 000000000..64af60318 --- /dev/null +++ b/components/core/tests/test-regex_utils.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include + +using clp::regex_utils::ErrorCode; +using clp::regex_utils::regex_to_wildcard; +using clp::regex_utils::RegexToWildcardTranslatorConfig; + +TEST_CASE("regex_to_wildcard_simple_translations", "[regex_utils][re2wc][simple_translations]") { + REQUIRE(regex_to_wildcard("").value().empty()); + + REQUIRE((regex_to_wildcard("xyz").value() == "xyz")); + REQUIRE((regex_to_wildcard(". xyz .* zyx .").value() == "? xyz * zyx ?")); + REQUIRE((regex_to_wildcard(". xyz .+ zyx .*").value() == "? xyz ?* zyx *")); +} + +TEST_CASE("regex_to_wildcard_unescaped_metachar", "[regex_utils][re2wc][unescaped_metachar]") { + REQUIRE((regex_to_wildcard(".? xyz .* zyx .").error() == ErrorCode::UnsupportedQuestionMark)); + REQUIRE((regex_to_wildcard(". xyz .** zyx .").error() == ErrorCode::UntranslatableStar)); + REQUIRE((regex_to_wildcard(". xyz .*+ zyx .").error() == ErrorCode::UntranslatablePlus)); + REQUIRE((regex_to_wildcard(". xyz |.* zyx .").error() == ErrorCode::UnsupportedPipe)); + REQUIRE((regex_to_wildcard(". xyz ^.* zyx .").error() == ErrorCode::IllegalCaret)); + REQUIRE((regex_to_wildcard(". xyz $.* zyx .").error() == ErrorCode::IllegalDollarSign)); +} + +TEST_CASE("regex_to_wildcard_escaped_metachar", "[regex_utils][re2wc][escaped_metachar]") { + // Escape backslash is superfluous for the following set of characters + REQUIRE((regex_to_wildcard("<>-_/=!").value() == "<>-_/=!")); + REQUIRE((regex_to_wildcard("\\<\\>\\-\\_\\/\\=\\!").value() == "<>-_/=!")); + // Test the full escape sequences set + REQUIRE( + (regex_to_wildcard("\\*\\+\\?\\|\\^\\$\\.\\{\\}\\[\\]\\(\\)\\<\\>\\-\\_\\/\\=\\!\\\\") + .value() + == "\\*+\\?|^$.{}[]()<>-_/=!\\\\") + ); + // Test unsupported escape sequences + REQUIRE( + (regex_to_wildcard("abc\\Qdefghi\\Ejkl").error() + == clp::regex_utils::ErrorCode::IllegalEscapeSequence) + ); +} + +TEST_CASE("regex_to_wildcard_charset", "[regex_utils][re2wc][charset]") { + REQUIRE((regex_to_wildcard("x[y]z").value() == "xyz")); + REQUIRE((regex_to_wildcard("x[\\^]z").value() == "x^z")); + REQUIRE((regex_to_wildcard("x[\\]]z").value() == "x]z")); + REQUIRE((regex_to_wildcard("x[-]z").value() == "x-z")); + REQUIRE((regex_to_wildcard("x[\\-]z").value() == "x-z")); + REQUIRE((regex_to_wildcard("x[\\\\]z").value() == "x\\\\z")); + REQUIRE((regex_to_wildcard("[a][b][\\^][-][\\-][\\]][\\\\][c][d]").value() == "ab^--]\\\\cd")); + + REQUIRE((regex_to_wildcard("x[]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[a-z]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[^^]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[^0-9]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("[xX][yY]").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9]").error() == ErrorCode::UnsupportedCharsetPattern)); + + REQUIRE((regex_to_wildcard("[\\").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("[\\\\").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("[xX").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9").error() == ErrorCode::IncompleteCharsetStructure)); +} + +TEST_CASE("regex_to_wildcard_case_insensitive_config", "[regex_utils][re2wc][case_insensitive]") { + RegexToWildcardTranslatorConfig const config{/*case_insensitive_wildcard=*/true, false}; + REQUIRE((regex_to_wildcard("[xX][yY]", config).value() == "xy")); + REQUIRE((regex_to_wildcard("[Yy][Xx]", config).value() == "yx")); + REQUIRE((regex_to_wildcard("[aA][Bb][Cc]", config).value() == "abc")); + REQUIRE((regex_to_wildcard("[aA][Bb][\\^][-][\\]][Cc][dD]", config).value() == "ab^-]cd")); + + REQUIRE((regex_to_wildcard("[xX").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE( + (regex_to_wildcard("[aA][Bb][^[-[\\[Cc[dD", config).error() + == ErrorCode::IncompleteCharsetStructure) + ); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9]").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE( + (regex_to_wildcard("[aA][Bb][^[-[\\[Cc[dD]", config).error() + == ErrorCode::UnsupportedCharsetPattern) + ); +} + +TEST_CASE("regex_to_wildcard_anchor_config", "[regex_utils][re2wc][anchor_config]") { + // Test anchors and prefix/suffix wildcards + RegexToWildcardTranslatorConfig const config{false, /*add_prefix_suffix_wildcards=*/true}; + REQUIRE(((regex_to_wildcard("^", config).value() == "*"))); + REQUIRE((regex_to_wildcard("$", config).value() == "*")); + REQUIRE((regex_to_wildcard("^xyz$", config).value() == "xyz")); + REQUIRE((regex_to_wildcard("xyz", config).value() == "*xyz*")); + REQUIRE((regex_to_wildcard("xyz$$", config).value() == "*xyz")); + + REQUIRE((regex_to_wildcard("xyz$zyx$", config).error() == ErrorCode::IllegalDollarSign)); +} diff --git a/components/core/tests/test-utf8_utils.cpp b/components/core/tests/test-utf8_utils.cpp new file mode 100644 index 000000000..77324eaf9 --- /dev/null +++ b/components/core/tests/test-utf8_utils.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../src/clp/ffi/utils.hpp" +#include "../src/clp/utf8_utils.hpp" + +using clp::ffi::validate_and_escape_utf8_string; +using clp::is_utf8_encoded; + +namespace { +/** + * @param raw + * @return The input string after escaping any characters that are invalid in JSON strings. + */ +[[nodiscard]] auto get_expected_escaped_string(std::string_view raw) -> std::string; + +/** + * Generates a UTF-8 encoded byte sequence with the given code point and number of continuation + * bytes. The range of the code point is not validated, which means the generated byte sequence can + * be invalid (overlong or exceeding the valid range of UTF-8 code points). + * @param code_point + * @param num_continuation_bytes + * @return The encoded UTF-8 byte sequence. + */ +[[nodiscard]] auto +generate_utf8_byte_sequence(uint32_t code_point, size_t num_continuation_bytes) -> std::string; + +auto get_expected_escaped_string(std::string_view raw) -> std::string { + nlohmann::json const json_str = raw; // Don't use '{}' initializer + auto const dumped_str{json_str.dump()}; + // Strip the quotes that nlohmann::json adds + return {dumped_str.begin() + 1, dumped_str.end() - 1}; +} + +auto generate_utf8_byte_sequence(uint32_t code_point, size_t num_continuation_bytes) + -> std::string { + REQUIRE((1 <= num_continuation_bytes && num_continuation_bytes <= 3)); + std::vector encoded_bytes; + while (encoded_bytes.size() < num_continuation_bytes) { + auto const least_significant_byte{static_cast(code_point)}; + encoded_bytes.push_back(static_cast( + (least_significant_byte & ~clp::cUtf8ContinuationByteMask) + | clp::cUtf8ContinuationByteHeader + )); + code_point >>= clp::cUtf8NumContinuationByteCodePointBits; + } + + uint8_t lead_byte_code_point_mask{}; + uint8_t lead_byte_header{}; + if (1 == num_continuation_bytes) { + lead_byte_code_point_mask = static_cast(~clp::cTwoByteUtf8CharHeaderMask); + lead_byte_header = clp::cTwoByteUtf8CharHeader; + } else if (2 == num_continuation_bytes) { + lead_byte_code_point_mask = static_cast(~clp::cThreeByteUtf8CharHeaderMask); + lead_byte_header = clp::cThreeByteUtf8CharHeader; + } else { // 3 == num_continuation_bytes + lead_byte_code_point_mask = static_cast(~clp::cFourByteUtf8CharHeaderMask); + lead_byte_header = clp::cFourByteUtf8CharHeader; + } + encoded_bytes.push_back(static_cast( + (static_cast(code_point) & lead_byte_code_point_mask) | lead_byte_header + )); + + return {encoded_bytes.rbegin(), encoded_bytes.rend()}; +} +} // namespace + +TEST_CASE("escape_utf8_string_basic", "[utf8_utils]") { + std::string test_str; + std::optional actual; + + // Test empty string + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test string that has nothing to escape + test_str = "This string has nothing to escape :)"; + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test string with all single byte UTF-8 characters, including those we escape. + test_str.clear(); + for (uint8_t i{0}; i <= static_cast(INT8_MAX); ++i) { + test_str.push_back(static_cast(i)); + } + // Shuffle characters randomly + // NOLINTNEXTLINE(cert-msc32-c, cert-msc51-cpp) + std::shuffle(test_str.begin(), test_str.end(), std::default_random_engine{}); + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test valid UTF-8 chars with continuation bytes + std::vector const valid_utf8{ + "\n", + "\xF0\xA0\x80\x8F", // https://en.wiktionary.org/wiki/%F0%A0%80%8F + "a", + "\xE4\xB8\xAD", // https://en.wiktionary.org/wiki/%E4%B8%AD + "\x1F", + "\xC2\xA2", // ¢ + "\\" + }; + test_str.clear(); + for (auto const& str : valid_utf8) { + test_str.append(str); + } + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); +} + +TEST_CASE("escape_utf8_string_with_invalid_continuation", "[utf8_utils]") { + std::string test_str; + + auto const valid_utf8_byte_sequence = GENERATE( + generate_utf8_byte_sequence(0x80, 1), + generate_utf8_byte_sequence(0x800, 2), + generate_utf8_byte_sequence(0x1'0000, 3) + ); + + // Test incomplete continuation bytes + auto const begin_it{valid_utf8_byte_sequence.cbegin()}; + std::string const valid{"Valid"}; + for (auto end_it{valid_utf8_byte_sequence.cend() - 1}; + valid_utf8_byte_sequence.cbegin() != end_it; + --end_it) + { + std::string const incomplete_byte_sequence{begin_it, end_it}; + + test_str = valid + incomplete_byte_sequence; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + + test_str = incomplete_byte_sequence + valid; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + } + + // Test invalid lead byte + test_str = valid_utf8_byte_sequence; + constexpr char cInvalidLeadByte{'\xFF'}; + test_str.front() = cInvalidLeadByte; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + + // Test invalid continuation bytes + for (size_t idx{1}; idx < valid_utf8_byte_sequence.size(); ++idx) { + test_str = valid_utf8_byte_sequence; + constexpr uint8_t cInvalidContinuationByteMask{0x40}; + test_str.at(idx) |= cInvalidContinuationByteMask; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + } +} + +TEST_CASE("validate_utf8_code_point_ranges", "[utf8_utils]") { + // Test 1 byte encoding code point range + for (auto code_point{clp::cOneByteUtf8CharCodePointLowerBound}; + code_point <= clp::cOneByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(std::string{static_cast(code_point)})); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 1)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 2 byte encoding code point range + for (auto code_point{clp::cTwoByteUtf8CharCodePointLowerBound}; + code_point <= clp::cTwoByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 1))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 3 byte encoding code point range + for (auto code_point{clp::cThreeByteUtf8CharCodePointLowerBound}; + code_point <= clp::cThreeByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 4 byte encoding code point range + for (auto code_point{clp::cFourByteUtf8CharCodePointLowerBound}; + code_point <= clp::cFourByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3))); + } + + // Test 4 byte encoding code point out of range + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + for (auto code_point{clp::cFourByteUtf8CharCodePointUpperBound + 1}; code_point <= 0x1F'FFFF; + ++code_point) + { + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } +} diff --git a/components/core/tests/test_log_files/test_no_floats_sorted.jsonl b/components/core/tests/test_log_files/test_no_floats_sorted.jsonl new file mode 100644 index 000000000..8dfcd85f6 --- /dev/null +++ b/components/core/tests/test_log_files/test_no_floats_sorted.jsonl @@ -0,0 +1,4 @@ +{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"empty_object":{},"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"null":null,"string":"short_string","true":true} +{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"nonempty_object":{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"empty_object":{},"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"null":null,"string":"short_string","true":true},"null":null,"string":"short_string","true":true} +{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"nonempty_object":{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"non_empty_object2":{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_array":[],"empty_object":{},"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"null":null,"string":"short_string","true":true},"null":null,"string":"short_string","true":true},"null":null,"string":"short_string","true":true} +{"clp_string":"uid=0, CPU usage:99.99%, \"user_name\"=YScope","empty_object":{},"false":false,"int16_max":32767,"int16_min":-32768,"int32_max":2147483647,"int32_min":-2147483648,"int64_max_jq_losslessly_represents":9824299763229016,"int64_min_jq_losslessly_represents":-9007199254740992,"int8_max":127,"int8_min":-128,"nonempty_array":[1,2,3,4,5],"null":null,"string":"short_string","true":true} diff --git a/components/core/tools/docker-images/clp-env-base-centos-stream-9/Dockerfile b/components/core/tools/docker-images/clp-env-base-centos-stream-9/Dockerfile new file mode 100644 index 000000000..e7b37fc32 --- /dev/null +++ b/components/core/tools/docker-images/clp-env-base-centos-stream-9/Dockerfile @@ -0,0 +1,22 @@ +FROM dokken/centos-stream-9 AS base + +WORKDIR /root + +RUN mkdir -p ./tools/scripts/lib_install +ADD ./tools/scripts/lib_install ./tools/scripts/lib_install + +RUN ./tools/scripts/lib_install/centos-stream-9/install-all.sh + +# NOTE: +# 1. `task` doesn't have an apt/dnf package so we use its install script. +# 2. We don't want to install it using `install-prebuilt-packages.sh` since users may use that on +# their own machines and it would change their environment in a way that can't easily be undone. +RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin + +# Remove cached files +RUN dnf clean all \ + && rm -rf /tmp/* /var/tmp/* + +# Flatten the image +FROM scratch +COPY --from=base / / diff --git a/components/core/tools/docker-images/clp-env-base-centos-stream-9/build.sh b/components/core/tools/docker-images/clp-env-base-centos-stream-9/build.sh new file mode 100755 index 000000000..fa9cc18ea --- /dev/null +++ b/components/core/tools/docker-images/clp-env-base-centos-stream-9/build.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Exit on any error +set -e + +# Error on undefined variable +set -u + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +component_root="${script_dir}/../../../" + +docker build \ + --tag clp-core-dependencies-x86-centos-stream-9:dev \ + "$component_root" \ + --file "${script_dir}/Dockerfile" diff --git a/components/core/tools/docker-images/clp-env-base-centos7.4/Dockerfile b/components/core/tools/docker-images/clp-env-base-centos7.4/Dockerfile deleted file mode 100644 index 1de75872a..000000000 --- a/components/core/tools/docker-images/clp-env-base-centos7.4/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM centos:centos7.4.1708 AS BASE - -WORKDIR /root - -RUN mkdir -p ./tools/docker-images/clp-env-base-centos7.4 -ADD ./tools/docker-images/clp-env-base-centos7.4/setup-scripts ./tools/docker-images/clp-env-base-centos7.4/setup-scripts - -RUN mkdir -p ./tools/scripts/lib_install -ADD ./tools/scripts/lib_install ./tools/scripts/lib_install - -RUN ./tools/scripts/lib_install/centos7.4/install-all.sh - -# Enable gcc 10 in login shells and non-interactive non-login shells -RUN ln -s /opt/rh/devtoolset-10/enable /etc/profile.d/devtoolset.sh - -# Enable git 2.27 -# NOTE: We use a script to enable the SCL git package on each git call because some Github actions -# cannot be forced to use a bash shell that loads .bashrc -RUN cp ./tools/docker-images/clp-env-base-centos7.4/setup-scripts/git /usr/bin/git - -# Remove cached files -RUN yum clean all \ - && rm -rf /tmp/* /var/tmp/* - -# Flatten the image -FROM scratch -COPY --from=BASE / / - -# Set PKG_CONFIG_PATH since CentOS doesn't look in /usr/local by default -ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig - -# Load .bashrc for non-interactive bash shells -ENV BASH_ENV=/etc/bashrc diff --git a/components/core/tools/docker-images/clp-env-base-centos7.4/build.sh b/components/core/tools/docker-images/clp-env-base-centos7.4/build.sh deleted file mode 100755 index 41f6bceae..000000000 --- a/components/core/tools/docker-images/clp-env-base-centos7.4/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -component_root=${script_dir}/../../../ - -docker build -t clp-core-dependencies-x86-centos7.4:dev ${component_root} --file ${script_dir}/Dockerfile diff --git a/components/core/tools/docker-images/clp-env-base-centos7.4/setup-scripts/git b/components/core/tools/docker-images/clp-env-base-centos7.4/setup-scripts/git deleted file mode 100755 index 8e18fb754..000000000 --- a/components/core/tools/docker-images/clp-env-base-centos7.4/setup-scripts/git +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -source /opt/rh/rh-git227/enable -git "$@" diff --git a/components/core/tools/docker-images/clp-env-base-ubuntu-focal/Dockerfile b/components/core/tools/docker-images/clp-env-base-ubuntu-focal/Dockerfile index f50ccadb4..e34deb336 100644 --- a/components/core/tools/docker-images/clp-env-base-ubuntu-focal/Dockerfile +++ b/components/core/tools/docker-images/clp-env-base-ubuntu-focal/Dockerfile @@ -16,6 +16,12 @@ RUN update-alternatives --set cpp /usr/bin/cpp-10 RUN ./tools/scripts/lib_install/ubuntu-focal/install-packages-from-source.sh +# NOTE: +# 1. `task` doesn't have an apt/yum package so we use its install script. +# 2. We don't want to install it using `install-prebuilt-packages.sh` since users may use that on +# their own machines and it would change their environment in a way that can't easily be undone. +RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin + # Remove cached files RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/components/core/tools/docker-images/clp-env-base-ubuntu-jammy/Dockerfile b/components/core/tools/docker-images/clp-env-base-ubuntu-jammy/Dockerfile index 61dec3228..affdf131d 100644 --- a/components/core/tools/docker-images/clp-env-base-ubuntu-jammy/Dockerfile +++ b/components/core/tools/docker-images/clp-env-base-ubuntu-jammy/Dockerfile @@ -7,6 +7,12 @@ ADD ./tools/scripts/lib_install ./tools/scripts/lib_install RUN ./tools/scripts/lib_install/ubuntu-jammy/install-all.sh +# NOTE: +# 1. `task` doesn't have an apt/yum package so we use its install script. +# 2. We don't want to install it using `install-prebuilt-packages.sh` since users may use that on +# their own machines and it would change their environment in a way that can't easily be undone. +RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin + # Remove cached files RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/components/core/tools/scripts/deps-download/Catch2.json b/components/core/tools/scripts/deps-download/Catch2.json deleted file mode 100644 index b26eb68e8..000000000 --- a/components/core/tools/scripts/deps-download/Catch2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.7.zip", - "unzip": true, - "targets": [ - { - "source": "Catch2-2.13.7", - "destination": "submodules/Catch2" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/abseil-cpp.json b/components/core/tools/scripts/deps-download/abseil-cpp.json deleted file mode 100644 index e38bf8bdb..000000000 --- a/components/core/tools/scripts/deps-download/abseil-cpp.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.1.zip", - "unzip": true, - "targets": [ - { - "source": "abseil-cpp-20230802.1", - "destination": "submodules/abseil-cpp" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/antlr4.json b/components/core/tools/scripts/deps-download/antlr4.json deleted file mode 100644 index ff0d4d871..000000000 --- a/components/core/tools/scripts/deps-download/antlr4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "url": "https://www.antlr.org/download/antlr-4.13.1-complete.jar", - "unzip": false, - "hash": { - "algo": "sha3_256", - "digest": "292ba55b3be8443777737e94841cff7a343e7067747c2cb6f58830797b20be65" - }, - "targets": [ - { - "source": "antlr-4.13.1-complete.jar", - "destination": "third-party/antlr/antlr-4.13.1-complete.jar" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/boost-outcome.json b/components/core/tools/scripts/deps-download/boost-outcome.json deleted file mode 100644 index 01e89b394..000000000 --- a/components/core/tools/scripts/deps-download/boost-outcome.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/boostorg/outcome/archive/refs/tags/boost-1.83.0.zip", - "unzip": true, - "targets": [ - { - "source": "outcome-boost-1.83.0", - "destination": "submodules/boost-outcome" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/date.json b/components/core/tools/scripts/deps-download/date.json deleted file mode 100644 index 127275b51..000000000 --- a/components/core/tools/scripts/deps-download/date.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/HowardHinnant/date/archive/refs/tags/v3.0.1.zip", - "unzip": true, - "targets": [ - { - "source": "date-3.0.1", - "destination": "submodules/date" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/download-all.sh b/components/core/tools/scripts/deps-download/download-all.sh deleted file mode 100755 index ded2b2612..000000000 --- a/components/core/tools/scripts/deps-download/download-all.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# Stop on error -set -e - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -project_root_dir="$script_dir"/../../../../../ -component_root_dir="$script_dir"/../../../ - -cd "${component_root_dir}" -mkdir -p submodules - -# We don't use a git submodule for sqlite3 since that would require building the -# sqlite amalgamation -python3 "${script_dir}/download-dep.py" "${script_dir}/sqlite3.json" -python3 "${script_dir}/download-dep.py" "${script_dir}/antlr4.json" - -if [ -e "$project_root_dir/.git" ] ; then - git submodule update --init --recursive -else - python3 "${script_dir}/download-dep.py" "${script_dir}/abseil-cpp.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/boost-outcome.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/Catch2.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/date.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/json.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/log-surgeon.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/simdjson.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/yaml-cpp.json" -fi diff --git a/components/core/tools/scripts/deps-download/download-dep.py b/components/core/tools/scripts/deps-download/download-dep.py deleted file mode 100644 index 3a8c011a1..000000000 --- a/components/core/tools/scripts/deps-download/download-dep.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import hashlib -import json -import logging -import mmap -import pathlib -import shutil -import sys -import uuid -import urllib.parse -import urllib.request - -# Setup logging -# Create logger -logger = logging.getLogger() -logger.setLevel(logging.INFO) -# Setup console logging -logging_console_handler = logging.StreamHandler() -logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") -logging_console_handler.setFormatter(logging_formatter) -logger.addHandler(logging_console_handler) - - -def hash_file(algo: str, path: pathlib.Path): - if "sha3_256" == algo: - hasher = hashlib.sha3_256() - with open(path, 'rb') as f: - with mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) as mapped_file: - hasher.update(mapped_file) - return hasher.hexdigest() - - -def main(argv): - script_dir = pathlib.Path(__file__).parent.resolve() - project_root_dir = script_dir.parent.parent.parent - - args_parser = argparse.ArgumentParser(description="Download dependency.") - args_parser.add_argument("config-file", help="Dependency configuration file.") - - parsed_args = vars(args_parser.parse_args(argv[1:])) - config_file_path = pathlib.Path(parsed_args["config-file"]).resolve() - - # Load configurations - with open(config_file_path) as f: - config = json.load(f) - - target_url = config["url"] - parsed_url = urllib.parse.urlparse(target_url) - filename = pathlib.Path(parsed_url.path).name - - extraction_dir = pathlib.Path("/") / "tmp" / str(uuid.uuid4()) - extraction_dir.mkdir(parents=True, exist_ok=True) - - # Download file - file_path = extraction_dir / filename - urllib.request.urlretrieve(target_url, file_path) - if config["unzip"]: - # NOTE: We need to convert file_path to a str since unpack_archive only - # accepts a path-like object on Python versions >= 3.7 - shutil.unpack_archive(str(file_path), extraction_dir) - - if "hash" in config: - # Verify hash - hash = hash_file(config["hash"]["algo"], file_path) - if hash != config["hash"]["digest"]: - logger.fatal("Hash mismatch.") - return -1 - - for target in config["targets"]: - target_source_path = extraction_dir / target["source"] - target_dest_path = project_root_dir / target["destination"] - - target_dest_parent = target_dest_path.parent - - # Remove destination - if target_dest_path.exists(): - shutil.rmtree(target_dest_path, ignore_errors=True) - else: - # Create destination parent - target_dest_parent.mkdir(parents=True, exist_ok=True) - - # Copy destination to target - if config["unzip"]: - shutil.copytree(target_source_path, target_dest_path) - else: - shutil.copy(target_source_path, target_dest_path) - - shutil.rmtree(extraction_dir) - - return 0 - - -if "__main__" == __name__: - sys.exit(main(sys.argv)) diff --git a/components/core/tools/scripts/deps-download/json.json b/components/core/tools/scripts/deps-download/json.json deleted file mode 100644 index 687c2697b..000000000 --- a/components/core/tools/scripts/deps-download/json.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.zip", - "unzip": true, - "targets": [ - { - "source": "json-3.11.3", - "destination": "submodules/json" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/log-surgeon.json b/components/core/tools/scripts/deps-download/log-surgeon.json deleted file mode 100644 index 3b8951951..000000000 --- a/components/core/tools/scripts/deps-download/log-surgeon.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/y-scope/log-surgeon/archive/895f464.zip", - "unzip": true, - "targets": [ - { - "source": "log-surgeon-895f46489b1911ab3b3aac3202afd56c96e8cd98", - "destination": "submodules/log-surgeon" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/simdjson.json b/components/core/tools/scripts/deps-download/simdjson.json deleted file mode 100644 index 8b9999961..000000000 --- a/components/core/tools/scripts/deps-download/simdjson.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "url": "https://github.com/simdjson/simdjson/archive/refs/tags/v3.6.3.zip", - "unzip": true, - "targets": [ - { - "source": "simdjson-3.6.3", - "destination": "submodules/simdjson" - } - ] -} - diff --git a/components/core/tools/scripts/deps-download/sqlite3.json b/components/core/tools/scripts/deps-download/sqlite3.json deleted file mode 100644 index 9b9391d89..000000000 --- a/components/core/tools/scripts/deps-download/sqlite3.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "url": "https://www.sqlite.org/2021/sqlite-amalgamation-3360000.zip", - "unzip": true, - "hash": { - "algo": "sha3_256", - "digest": "d25609210ec93b3c8c7da66a03cf82e2c9868cfbd2d7d866982861855e96f972" - }, - "targets": [ - { - "source": "sqlite-amalgamation-3360000", - "destination": "submodules/sqlite3" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/yaml-cpp.json b/components/core/tools/scripts/deps-download/yaml-cpp.json deleted file mode 100644 index e9b4138c8..000000000 --- a/components/core/tools/scripts/deps-download/yaml-cpp.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip", - "unzip": true, - "targets": [ - { - "source": "yaml-cpp-yaml-cpp-0.7.0", - "destination": "submodules/yaml-cpp" - } - ] -} diff --git a/components/core/tools/scripts/lib_install/centos7.4/install-all.sh b/components/core/tools/scripts/lib_install/centos-stream-9/install-all.sh similarity index 63% rename from components/core/tools/scripts/lib_install/centos7.4/install-all.sh rename to components/core/tools/scripts/lib_install/centos-stream-9/install-all.sh index e338a30d7..c50fb81cc 100755 --- a/components/core/tools/scripts/lib_install/centos7.4/install-all.sh +++ b/components/core/tools/scripts/lib_install/centos-stream-9/install-all.sh @@ -8,5 +8,5 @@ set -u script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -"$script_dir"/install-prebuilt-packages.sh -"$script_dir"/install-packages-from-source.sh +"${script_dir}/install-prebuilt-packages.sh" +"${script_dir}/install-packages-from-source.sh" diff --git a/components/core/tools/scripts/lib_install/centos-stream-9/install-packages-from-source.sh b/components/core/tools/scripts/lib_install/centos-stream-9/install-packages-from-source.sh new file mode 100755 index 000000000..f2965f9fd --- /dev/null +++ b/components/core/tools/scripts/lib_install/centos-stream-9/install-packages-from-source.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Exit on any error +set -e + +# Error on undefined variable +set -u + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +lib_install_scripts_dir="${script_dir}/.." + +# NOTE: The remaining installation scripts depend on boost, so we install it beforehand. +"${lib_install_scripts_dir}/install-boost.sh" 1.76.0 + +"${lib_install_scripts_dir}/fmtlib.sh" 8.0.1 +"${lib_install_scripts_dir}/spdlog.sh" 1.9.2 +"${lib_install_scripts_dir}/mongocxx.sh" 3.10.2 +"${lib_install_scripts_dir}/msgpack.sh" 7.0.0 diff --git a/components/core/tools/scripts/lib_install/centos-stream-9/install-prebuilt-packages.sh b/components/core/tools/scripts/lib_install/centos-stream-9/install-prebuilt-packages.sh new file mode 100755 index 000000000..66ea4ac4f --- /dev/null +++ b/components/core/tools/scripts/lib_install/centos-stream-9/install-prebuilt-packages.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Exit on any error +set -e + +# Error on undefined variable +set -u + +dnf install -y \ + cmake \ + diffutils \ + gcc-c++ \ + git \ + java-11-openjdk \ + jq \ + libarchive-devel \ + libcurl-devel \ + libzstd-devel \ + make \ + mariadb-connector-c-devel \ + openssl-devel diff --git a/components/core/tools/scripts/lib_install/centos7.4/install-packages-from-source.sh b/components/core/tools/scripts/lib_install/centos7.4/install-packages-from-source.sh deleted file mode 100755 index 08c1cafb6..000000000 --- a/components/core/tools/scripts/lib_install/centos7.4/install-packages-from-source.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -# Exit on any error -set -e - -# Enable gcc 10 -source /opt/rh/devtoolset-10/enable - -# Enable git -source /opt/rh/rh-git227/enable - -# Error on undefined variable -# NOTE: We enable this *after* sourcing the scripts above since we can't guarantee they won't have -# unbound variables in them. -set -u - -script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -lib_install_scripts_dir=$script_dir/.. - -# NOTE: The remaining installation scripts depend on curl, so we install it first. -"$lib_install_scripts_dir"/install-curl.sh 8.8.0 - -# NOTE: The remaining installation scripts depend on cmake and boost, so we install them beforehand. -"$lib_install_scripts_dir"/install-cmake.sh 3.21.2 -"$lib_install_scripts_dir"/install-boost.sh 1.76.0 - -"$lib_install_scripts_dir"/fmtlib.sh 8.0.1 -"$lib_install_scripts_dir"/libarchive.sh 3.5.1 -"$lib_install_scripts_dir"/lz4.sh 1.8.2 -"$lib_install_scripts_dir"/mariadb-connector-c.sh 3.2.3 -"$lib_install_scripts_dir"/mongoc.sh 1.24.4 -"$lib_install_scripts_dir"/mongocxx.sh 3.8.0 -"$lib_install_scripts_dir"/msgpack.sh 6.0.0 -"$lib_install_scripts_dir"/spdlog.sh 1.9.2 -"$lib_install_scripts_dir"/zstandard.sh 1.4.9 diff --git a/components/core/tools/scripts/lib_install/centos7.4/install-prebuilt-packages.sh b/components/core/tools/scripts/lib_install/centos7.4/install-prebuilt-packages.sh deleted file mode 100755 index f7d14ad7c..000000000 --- a/components/core/tools/scripts/lib_install/centos7.4/install-prebuilt-packages.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# Exit on any error -set -e - -# Error on undefined variable -set -u - -yum install -y \ - bzip2 \ - centos-release-scl \ - java-11-openjdk \ - make \ - openssl-devel \ - openssl-static \ - python3 \ - python3-pip \ - rsync \ - zlib-static - -# Install packages from CentOS' software collections repository (centos-release-scl) -yum install -y \ - devtoolset-10 \ - rh-git227 diff --git a/components/core/tools/scripts/lib_install/install-boost.sh b/components/core/tools/scripts/lib_install/install-boost.sh index 064368725..9e5f9a1c5 100755 --- a/components/core/tools/scripts/lib_install/install-boost.sh +++ b/components/core/tools/scripts/lib_install/install-boost.sh @@ -34,7 +34,7 @@ tar xzf ${tar_filename} cd boost_${version_with_underscores} # Build -./bootstrap.sh --with-libraries=filesystem,iostreams,program_options,system +./bootstrap.sh --with-libraries=filesystem,iostreams,program_options,regex,system ./b2 -j${num_cpus} # Install diff --git a/components/core/tools/scripts/lib_install/macos-12/install-all.sh b/components/core/tools/scripts/lib_install/macos-12/install-all.sh deleted file mode 100755 index c13223ba6..000000000 --- a/components/core/tools/scripts/lib_install/macos-12/install-all.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# Exit on any error -set -e - -# Error on undefined variable -set -u - -brew update -brew install \ - boost \ - cmake \ - fmt \ - gcc \ - java11 \ - libarchive \ - lz4 \ - mariadb-connector-c \ - mongo-cxx-driver \ - msgpack-cxx \ - spdlog \ - pkg-config \ - zstd diff --git a/components/core/tools/scripts/lib_install/macos/install-all.sh b/components/core/tools/scripts/lib_install/macos/install-all.sh new file mode 100755 index 000000000..97e41903d --- /dev/null +++ b/components/core/tools/scripts/lib_install/macos/install-all.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Exit on any error +set -e + +# Error on undefined variable +set -u + +brew update +brew install \ + boost \ + cmake \ + coreutils \ + fmt \ + gcc \ + go-task \ + java11 \ + libarchive \ + lz4 \ + mariadb-connector-c \ + mongo-cxx-driver \ + msgpack-cxx \ + spdlog \ + zstd + +# Install pkg-config if it isn't already installed +# NOTE: We might expect that pkg-config is installed through brew, so trying to install it again +# would be harmless; however, in certain environments, like the macOS GitHub hosted runner, +# pkg-config is installed by other means, meaning a brew install would cause conflicts. +if ! command -v pkg-config ; then + brew install pkg-config +fi diff --git a/components/core/tools/scripts/lib_install/mariadb-connector-c.sh b/components/core/tools/scripts/lib_install/mariadb-connector-c.sh index 7ec16f409..4159f5f9b 100755 --- a/components/core/tools/scripts/lib_install/mariadb-connector-c.sh +++ b/components/core/tools/scripts/lib_install/mariadb-connector-c.sh @@ -47,8 +47,9 @@ fi source /etc/os-release if [ $ID = "ubuntu" ] ; then os_version=ubuntu-$UBUNTU_CODENAME -elif [ $ID = "centos" ] ; then - os_version=centos${VERSION_ID} +else + echo "Unsupported OS ID: $ID" + exit 1 fi # Download diff --git a/components/core/tools/scripts/lib_install/mongoc.sh b/components/core/tools/scripts/lib_install/mongoc.sh deleted file mode 100755 index c9a784344..000000000 --- a/components/core/tools/scripts/lib_install/mongoc.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env bash - -# Dependencies: -# - cmake -# - curl -# - git -# - g++ -# NOTE: Dependencies should be installed outside the script to allow the script to be largely -# distro-agnostic - -# Exit on any error -set -e - -# Error on undefined variable -set -u - -cUsage="Usage: ${BASH_SOURCE[0]} [ <.deb output directory>]" -if [ "$#" -lt 1 ] ; then - echo "$cUsage" - exit -fi -version=$1 - -package_name=libmongoc-dev -temp_dir="/tmp/${package_name}-installation" -deb_output_dir="${temp_dir}" -if [[ "$#" -gt 1 ]] ; then - deb_output_dir="$(readlink -f "$2")" - if [ ! -d "${deb_output_dir}" ] ; then - echo "${deb_output_dir} does not exist or is not a directory" - exit - fi -fi - -# Check if already installed -set +e -dpkg -l "${package_name}" | grep "${version}" -installed=$? -set -e -if [ $installed -eq 0 ] ; then - # Nothing to do - exit -fi - -echo "Checking for elevated privileges..." -install_cmd_args=() -if [ ${EUID:-$(id -u)} -ne 0 ] ; then - sudo echo "Script can elevate privileges." - install_cmd_args+=("sudo") -fi - -# Download -mkdir -p "$temp_dir" -cd "$temp_dir" -extracted_dir="${temp_dir}/mongo-c-driver-${version}" -if [ ! -e "${extracted_dir}" ] ; then - tar_filename="mongo-c-driver-${version}.tar.gz" - if [ ! -e "${tar_filename}" ] ; then - curl \ - -fsSL \ - "https://github.com/mongodb/mongo-c-driver/releases/download/${version}/${tar_filename}" \ - -o "${tar_filename}" - fi - - tar -xf "${tar_filename}" -fi - -# Set up -cd "${extracted_dir}" -mkdir -p build -cd build -cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF \ - -DENABLE_ICU=OFF \ - -DENABLE_TESTS=OFF \ - .. - -# Check if checkinstall is installed -set +e -command -v checkinstall -checkinstall_installed=$? -set -e - -# Install -if [ $checkinstall_installed -eq 0 ] ; then - install_cmd_args+=( - checkinstall - --pkgname "${package_name}" - --pkgversion "${version}" - --provides "${package_name}" - --nodoc - -y - --pakdir "${deb_output_dir}" - ) -fi -install_cmd_args+=( - cmake - --build . - --target install - --parallel -) -"${install_cmd_args[@]}" - -# Clean up -rm -rf "$temp_dir" diff --git a/components/core/tools/scripts/lib_install/mongocxx.sh b/components/core/tools/scripts/lib_install/mongocxx.sh index da41ef71d..8a06fcc76 100755 --- a/components/core/tools/scripts/lib_install/mongocxx.sh +++ b/components/core/tools/scripts/lib_install/mongocxx.sh @@ -67,12 +67,9 @@ fi # Set up cd "${extracted_dir}/build" -# NOTE: Although the mongocxx docs indicate we should use -# '-DMONGOCXX_OVERRIDE_DEFAULT_INSTALL_PREFIX=OFF' to install to the default location (/usr/local), -# this doesn't seem to work, so we specify CMAKE_INSTALL_PREFIX here cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DMONGOCXX_OVERRIDE_DEFAULT_INSTALL_PREFIX=OFF \ -DBUILD_SHARED_AND_STATIC_LIBS=ON \ -DBUILD_SHARED_LIBS_WITH_STATIC_MONGOC=ON \ -DENABLE_TESTS=OFF \ diff --git a/components/core/tools/scripts/lib_install/ubuntu-focal/install-packages-from-source.sh b/components/core/tools/scripts/lib_install/ubuntu-focal/install-packages-from-source.sh index 1281d0267..1e21314cc 100755 --- a/components/core/tools/scripts/lib_install/ubuntu-focal/install-packages-from-source.sh +++ b/components/core/tools/scripts/lib_install/ubuntu-focal/install-packages-from-source.sh @@ -15,8 +15,7 @@ lib_install_scripts_dir=$script_dir/.. "$lib_install_scripts_dir"/fmtlib.sh 8.0.1 "$lib_install_scripts_dir"/libarchive.sh 3.5.1 "$lib_install_scripts_dir"/lz4.sh 1.8.2 -"$lib_install_scripts_dir"/mongoc.sh 1.24.4 -"$lib_install_scripts_dir"/mongocxx.sh 3.8.0 -"$lib_install_scripts_dir"/msgpack.sh 6.0.0 +"$lib_install_scripts_dir"/mongocxx.sh 3.10.2 +"$lib_install_scripts_dir"/msgpack.sh 7.0.0 "$lib_install_scripts_dir"/spdlog.sh 1.9.2 "$lib_install_scripts_dir"/zstandard.sh 1.4.9 diff --git a/components/core/tools/scripts/lib_install/ubuntu-focal/install-prebuilt-packages.sh b/components/core/tools/scripts/lib_install/ubuntu-focal/install-prebuilt-packages.sh index 706674764..8997ffe01 100755 --- a/components/core/tools/scripts/lib_install/ubuntu-focal/install-prebuilt-packages.sh +++ b/components/core/tools/scripts/lib_install/ubuntu-focal/install-prebuilt-packages.sh @@ -17,6 +17,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ gcc \ gcc-10 \ git \ + jq \ libcurl4 \ libcurl4-openssl-dev \ libmariadb-dev \ diff --git a/components/core/tools/scripts/lib_install/ubuntu-jammy/install-packages-from-source.sh b/components/core/tools/scripts/lib_install/ubuntu-jammy/install-packages-from-source.sh index 527f392a5..7799c9ba5 100755 --- a/components/core/tools/scripts/lib_install/ubuntu-jammy/install-packages-from-source.sh +++ b/components/core/tools/scripts/lib_install/ubuntu-jammy/install-packages-from-source.sh @@ -12,8 +12,7 @@ lib_install_scripts_dir=$script_dir/.. "$lib_install_scripts_dir"/fmtlib.sh 8.0.1 "$lib_install_scripts_dir"/libarchive.sh 3.5.1 "$lib_install_scripts_dir"/lz4.sh 1.8.2 -"$lib_install_scripts_dir"/mongoc.sh 1.24.4 -"$lib_install_scripts_dir"/mongocxx.sh 3.8.0 -"$lib_install_scripts_dir"/msgpack.sh 6.0.0 +"$lib_install_scripts_dir"/mongocxx.sh 3.10.2 +"$lib_install_scripts_dir"/msgpack.sh 7.0.0 "$lib_install_scripts_dir"/spdlog.sh 1.9.2 "$lib_install_scripts_dir"/zstandard.sh 1.4.9 diff --git a/components/core/tools/scripts/lib_install/ubuntu-jammy/install-prebuilt-packages.sh b/components/core/tools/scripts/lib_install/ubuntu-jammy/install-prebuilt-packages.sh index 92d965b9b..9ed6b9b10 100755 --- a/components/core/tools/scripts/lib_install/ubuntu-jammy/install-prebuilt-packages.sh +++ b/components/core/tools/scripts/lib_install/ubuntu-jammy/install-prebuilt-packages.sh @@ -14,6 +14,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ curl \ build-essential \ git \ + jq \ libboost-filesystem-dev \ libboost-iostreams-dev \ libboost-program-options-dev \ diff --git a/components/core/tools/scripts/utils/build-and-run-unit-tests.py b/components/core/tools/scripts/utils/build-and-run-unit-tests.py new file mode 100644 index 000000000..7c4b13617 --- /dev/null +++ b/components/core/tools/scripts/utils/build-and-run-unit-tests.py @@ -0,0 +1,101 @@ +import argparse +import logging +import subprocess +import sys +from pathlib import Path +from typing import List, Optional + +# Set up console logging +logging_console_handler = logging.StreamHandler() +logging_formatter = logging.Formatter( + "%(asctime)s.%(msecs)03d %(levelname)s [%(module)s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%S" +) +logging_console_handler.setFormatter(logging_formatter) + +# Set up root logger +root_logger = logging.getLogger() +root_logger.setLevel(logging.INFO) +root_logger.addHandler(logging_console_handler) + +# Create logger +logger = logging.getLogger(__name__) + + +def _config_cmake_project(src_dir: Path, build_dir: Path, use_shared_libs: bool): + cmd = [ + "cmake", + "-S", + str(src_dir), + "-B", + str(build_dir), + ] + if use_shared_libs: + cmd.append("-DCLP_USE_STATIC_LIBS=OFF") + subprocess.run(cmd, check=True) + + +def _build_project(build_dir: Path, num_jobs: Optional[int]): + """ + :param build_dir: + :param num_jobs: Max number of jobs to run when building. + """ + + cmd = [ + "cmake", + "--build", + str(build_dir), + "--parallel", + ] + if num_jobs is not None: + cmd.append(str(num_jobs)) + subprocess.run(cmd, check=True) + + +def _run_unit_tests(build_dir: Path, test_spec: Optional[str]): + """ + :param build_dir: + :param test_spec: Catch2 test specification. + """ + + cmd = [ + "./unitTest", + ] + if test_spec is not None: + cmd.append(test_spec) + subprocess.run(cmd, cwd=build_dir, check=True) + + +def main(argv: List[str]) -> int: + args_parser = argparse.ArgumentParser( + description="Builds the CLP-core's binaries and runs its unit tests." + ) + args_parser.add_argument( + "--source-dir", required=True, help="Directory containing the main CMakeLists.txt." + ) + args_parser.add_argument("--build-dir", required=True, help="Build output directory.") + args_parser.add_argument( + "--use-shared-libs", + action="store_true", + help="Build targets by linking against shared libraries.", + ) + args_parser.add_argument( + "--num-jobs", type=int, help="Max number of jobs to run when building." + ) + args_parser.add_argument("--test-spec", help="Catch2 test specification.") + + parsed_args = args_parser.parse_args(argv[1:]) + src_dir: Path = Path(parsed_args.source_dir) + build_dir: Path = Path(parsed_args.build_dir) + use_shared_libs: bool = parsed_args.use_shared_libs + num_jobs: Optional[int] = parsed_args.num_jobs + test_spec: Optional[str] = parsed_args.test_spec + + _config_cmake_project(src_dir, build_dir, use_shared_libs) + _build_project(build_dir, num_jobs) + _run_unit_tests(build_dir, test_spec) + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/components/core/tools/scripts/utils/build-and-run-unit-tests.sh b/components/core/tools/scripts/utils/build-and-run-unit-tests.sh deleted file mode 100755 index aae94e0ad..000000000 --- a/components/core/tools/scripts/utils/build-and-run-unit-tests.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Exit on any error -set -e -# Error on undefined variable -set -u - -cUsage="Usage: ${BASH_SOURCE[0]} [ ]" -if [ "$#" -lt 2 ]; then - echo "$cUsage" - exit 1 -fi -src_dir="$1" -build_dir="$2" -if [ "$#" -gt 2 ]; then - unit_tests_filter="$3" -fi - -cmake -S "$src_dir" -B "$build_dir" -cmake --build "$build_dir" --parallel "$(getconf _NPROCESSORS_ONLN)" -cd "$build_dir" -if [ -z "${unit_tests_filter+x}" ]; then - ./unitTest -else - ./unitTest "$unit_tests_filter" -fi diff --git a/components/job-orchestration/job_orchestration/executor/search/__init__.py b/components/job-orchestration/job_orchestration/executor/query/__init__.py similarity index 100% rename from components/job-orchestration/job_orchestration/executor/search/__init__.py rename to components/job-orchestration/job_orchestration/executor/query/__init__.py diff --git a/components/job-orchestration/job_orchestration/executor/search/celery.py b/components/job-orchestration/job_orchestration/executor/query/celery.py similarity index 85% rename from components/job-orchestration/job_orchestration/executor/search/celery.py rename to components/job-orchestration/job_orchestration/executor/query/celery.py index 81a36d815..7d09e03ea 100644 --- a/components/job-orchestration/job_orchestration/executor/search/celery.py +++ b/components/job-orchestration/job_orchestration/executor/query/celery.py @@ -2,7 +2,7 @@ from . import celeryconfig -app = Celery("search") +app = Celery("query") app.config_from_object(celeryconfig) if "__main__" == __name__: diff --git a/components/job-orchestration/job_orchestration/executor/search/celeryconfig.py b/components/job-orchestration/job_orchestration/executor/query/celeryconfig.py similarity index 69% rename from components/job-orchestration/job_orchestration/executor/search/celeryconfig.py rename to components/job-orchestration/job_orchestration/executor/query/celeryconfig.py index 3dd99e670..6cf97dbd0 100644 --- a/components/job-orchestration/job_orchestration/executor/search/celeryconfig.py +++ b/components/job-orchestration/job_orchestration/executor/query/celeryconfig.py @@ -2,10 +2,14 @@ from job_orchestration.scheduler.constants import QueueName -imports = "job_orchestration.executor.search.fs_search_task" +imports = ( + "job_orchestration.executor.query.fs_search_task", + "job_orchestration.executor.query.extract_stream_task", +) task_routes = { - "job_orchestration.executor.search.fs_search_task.search": QueueName.SEARCH, + "job_orchestration.executor.query.fs_search_task.search": QueueName.QUERY, + "job_orchestration.executor.query.extract_stream_task.extract_stream": QueueName.QUERY, } task_create_missing_queues = True diff --git a/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py b/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py new file mode 100644 index 000000000..423ebb757 --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py @@ -0,0 +1,135 @@ +import datetime +import os +from pathlib import Path +from typing import Any, Dict, List, Optional + +from celery.app.task import Task +from celery.utils.log import get_task_logger +from clp_py_utils.clp_config import Database, StorageEngine +from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.celery import app +from job_orchestration.executor.query.utils import ( + report_command_creation_failure, + run_query_task, +) +from job_orchestration.scheduler.job_config import ExtractIrJobConfig, ExtractJsonJobConfig +from job_orchestration.scheduler.scheduler_data import QueryTaskStatus + +# Setup logging +logger = get_task_logger(__name__) + + +def make_command( + storage_engine: str, + clp_home: Path, + archives_dir: Path, + archive_id: str, + stream_output_dir: Path, + job_config: dict, + results_cache_uri: str, + stream_collection_name: str, +) -> Optional[List[str]]: + if StorageEngine.CLP == storage_engine: + logger.info("Starting IR extraction") + extract_ir_config = ExtractIrJobConfig.parse_obj(job_config) + if not extract_ir_config.file_split_id: + logger.error("file_split_id not supplied") + return None + command = [ + str(clp_home / "bin" / "clo"), + "i", + str(archives_dir / archive_id), + extract_ir_config.file_split_id, + str(stream_output_dir), + results_cache_uri, + stream_collection_name, + ] + if extract_ir_config.target_uncompressed_size is not None: + command.append("--target-size") + command.append(str(extract_ir_config.target_uncompressed_size)) + elif StorageEngine.CLP_S == storage_engine: + logger.info("Starting JSON extraction") + extract_json_config = ExtractJsonJobConfig.parse_obj(job_config) + command = [ + str(clp_home / "bin" / "clp-s"), + "x", + str(archives_dir), + str(stream_output_dir), + "--ordered", + "--archive-id", + archive_id, + "--mongodb-uri", + results_cache_uri, + "--mongodb-collection", + stream_collection_name, + ] + if extract_json_config.target_chunk_size is not None: + command.append("--target-ordered-chunk-size") + command.append(str(extract_json_config.target_chunk_size)) + else: + logger.error(f"Unsupported storage engine {storage_engine}") + return None + + return command + + +@app.task(bind=True) +def extract_stream( + self: Task, + job_id: str, + task_id: int, + job_config: dict, + archive_id: str, + clp_metadata_db_conn_params: dict, + results_cache_uri: str, +) -> Dict[str, Any]: + task_name = "Stream Extraction" + + # Setup logging to file + clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) + clp_logging_level = os.getenv("CLP_LOGGING_LEVEL") + set_logging_level(logger, clp_logging_level) + + logger.info(f"Started {task_name} task for job {job_id}") + + start_time = datetime.datetime.now() + task_status: QueryTaskStatus + sql_adapter = SQL_Adapter(Database.parse_obj(clp_metadata_db_conn_params)) + + # Make task_command + clp_home = Path(os.getenv("CLP_HOME")) + archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) + clp_storage_engine = os.getenv("CLP_STORAGE_ENGINE") + stream_output_dir = Path(os.getenv("CLP_STREAM_OUTPUT_DIR")) + stream_collection_name = os.getenv("CLP_STREAM_COLLECTION_NAME") + + task_command = make_command( + storage_engine=clp_storage_engine, + clp_home=clp_home, + archives_dir=archive_directory, + archive_id=archive_id, + stream_output_dir=stream_output_dir, + job_config=job_config, + results_cache_uri=results_cache_uri, + stream_collection_name=stream_collection_name, + ) + if not task_command: + return report_command_creation_failure( + sql_adapter=sql_adapter, + logger=logger, + task_name=task_name, + task_id=task_id, + start_time=start_time, + ) + + return run_query_task( + sql_adapter=sql_adapter, + logger=logger, + clp_logs_dir=clp_logs_dir, + task_command=task_command, + task_name=task_name, + job_id=job_id, + task_id=task_id, + start_time=start_time, + ) diff --git a/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py new file mode 100644 index 000000000..598bfdcfc --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py @@ -0,0 +1,152 @@ +import datetime +import os +from pathlib import Path +from typing import Any, Dict, List, Optional + +from celery.app.task import Task +from celery.utils.log import get_task_logger +from clp_py_utils.clp_config import Database, StorageEngine +from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.celery import app +from job_orchestration.executor.query.utils import ( + report_command_creation_failure, + run_query_task, +) +from job_orchestration.scheduler.job_config import SearchJobConfig +from job_orchestration.scheduler.scheduler_data import QueryTaskStatus + +# Setup logging +logger = get_task_logger(__name__) + + +def make_command( + storage_engine: str, + clp_home: Path, + archives_dir: Path, + archive_id: str, + search_config: SearchJobConfig, + results_cache_uri: str, + results_collection: str, +) -> Optional[List[str]]: + if StorageEngine.CLP == storage_engine: + command = [str(clp_home / "bin" / "clo"), "s", str(archives_dir / archive_id)] + if search_config.path_filter is not None: + command.append("--file-path") + command.append(search_config.path_filter) + elif StorageEngine.CLP_S == storage_engine: + command = [ + str(clp_home / "bin" / "clp-s"), + "s", + str(archives_dir), + "--archive-id", + archive_id, + ] + else: + logger.error(f"Unsupported storage engine {storage_engine}") + return None + + command.append(search_config.query_string) + if search_config.begin_timestamp is not None: + command.append("--tge") + command.append(str(search_config.begin_timestamp)) + if search_config.end_timestamp is not None: + command.append("--tle") + command.append(str(search_config.end_timestamp)) + if search_config.ignore_case: + command.append("--ignore-case") + + if search_config.aggregation_config is not None: + aggregation_config = search_config.aggregation_config + if aggregation_config.do_count_aggregation is not None: + command.append("--count") + if aggregation_config.count_by_time_bucket_size is not None: + command.append("--count-by-time") + command.append(str(aggregation_config.count_by_time_bucket_size)) + + # fmt: off + command.extend(( + "reducer", + "--host", aggregation_config.reducer_host, + "--port", str(aggregation_config.reducer_port), + "--job-id", str(aggregation_config.job_id) + )) + # fmt: on + elif search_config.network_address is not None: + # fmt: off + command.extend(( + "network", + "--host", search_config.network_address[0], + "--port", str(search_config.network_address[1]) + )) + # fmt: on + else: + # fmt: off + command.extend(( + "results-cache", + "--uri", results_cache_uri, + "--collection", results_collection, + "--max-num-results", str(search_config.max_num_results) + )) + # fmt: on + + return command + + +@app.task(bind=True) +def search( + self: Task, + job_id: str, + task_id: int, + job_config: dict, + archive_id: str, + clp_metadata_db_conn_params: dict, + results_cache_uri: str, +) -> Dict[str, Any]: + task_name = "search" + + # Setup logging to file + clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) + clp_logging_level = os.getenv("CLP_LOGGING_LEVEL") + set_logging_level(logger, clp_logging_level) + + logger.info(f"Started {task_name} task for job {job_id}") + + start_time = datetime.datetime.now() + task_status: QueryTaskStatus + sql_adapter = SQL_Adapter(Database.parse_obj(clp_metadata_db_conn_params)) + + # Make task_command + clp_home = Path(os.getenv("CLP_HOME")) + archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) + clp_storage_engine = os.getenv("CLP_STORAGE_ENGINE") + search_config = SearchJobConfig.parse_obj(job_config) + + task_command = make_command( + storage_engine=clp_storage_engine, + clp_home=clp_home, + archives_dir=archive_directory, + archive_id=archive_id, + search_config=search_config, + results_cache_uri=results_cache_uri, + results_collection=job_id, + ) + if not task_command: + return report_command_creation_failure( + sql_adapter=sql_adapter, + logger=logger, + task_name=task_name, + task_id=task_id, + start_time=start_time, + ) + + return run_query_task( + sql_adapter=sql_adapter, + logger=logger, + clp_logs_dir=clp_logs_dir, + task_command=task_command, + task_name=task_name, + job_id=job_id, + task_id=task_id, + start_time=start_time, + ) diff --git a/components/job-orchestration/job_orchestration/executor/query/utils.py b/components/job-orchestration/job_orchestration/executor/query/utils.py new file mode 100644 index 000000000..69d22398e --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/utils.py @@ -0,0 +1,135 @@ +import datetime +import os +import signal +import subprocess +import sys +from contextlib import closing +from logging import Logger +from pathlib import Path +from typing import Any, Dict, List + +from clp_py_utils.clp_config import QUERY_TASKS_TABLE_NAME +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.scheduler_data import QueryTaskResult, QueryTaskStatus + + +def get_task_log_file_path(clp_logs_dir: Path, job_id: str, task_id: int) -> Path: + worker_logs_dir = clp_logs_dir / job_id + worker_logs_dir.mkdir(exist_ok=True, parents=True) + return worker_logs_dir / f"{task_id}-clo.log" + + +def report_command_creation_failure( + sql_adapter: SQL_Adapter, + logger: Logger, + task_name: str, + task_id: int, + start_time: datetime.datetime, +): + logger.error(f"Error creating {task_name} command") + task_status = QueryTaskStatus.FAILED + update_query_task_metadata( + sql_adapter, + task_id, + dict(status=task_status, duration=0, start_time=start_time), + ) + + return QueryTaskResult( + task_id=task_id, + status=task_status, + duration=0, + ).dict() + + +def run_query_task( + sql_adapter: SQL_Adapter, + logger: Logger, + clp_logs_dir: Path, + task_command: List[str], + task_name: str, + job_id: str, + task_id: int, + start_time: datetime.datetime, +): + clo_log_path = get_task_log_file_path(clp_logs_dir, job_id, task_id) + clo_log_file = open(clo_log_path, "w") + + task_status = QueryTaskStatus.RUNNING + update_query_task_metadata( + sql_adapter, task_id, dict(status=task_status, start_time=start_time) + ) + + logger.info(f'Running: {" ".join(task_command)}') + task_proc = subprocess.Popen( + task_command, + preexec_fn=os.setpgrp, + close_fds=True, + stdout=clo_log_file, + stderr=clo_log_file, + ) + + def sigterm_handler(_signo, _stack_frame): + logger.debug("Entered sigterm handler") + if task_proc.poll() is None: + logger.debug(f"Trying to kill {task_name} process") + # Kill the process group in case the task process also forked + os.killpg(os.getpgid(task_proc.pid), signal.SIGTERM) + os.waitpid(task_proc.pid, 0) + logger.info(f"Cancelling {task_name} task.") + # Add 128 to follow convention for exit codes from signals + # https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549 + sys.exit(_signo + 128) + + # Register the function to kill the child process at exit + signal.signal(signal.SIGTERM, sigterm_handler) + + logger.info(f"Waiting for {task_name} to finish") + # `communicate` is equivalent to `wait` in this case, but avoids deadlocks if we switch to + # piping stdout/stderr in the future. + task_proc.communicate() + return_code = task_proc.returncode + if 0 != return_code: + task_status = QueryTaskStatus.FAILED + logger.error( + f"{task_name} task {task_id} failed for job {job_id} - return_code={return_code}" + ) + else: + task_status = QueryTaskStatus.SUCCEEDED + logger.info(f"{task_name} task {task_id} completed for job {job_id}") + + clo_log_file.close() + duration = (datetime.datetime.now() - start_time).total_seconds() + + update_query_task_metadata( + sql_adapter, task_id, dict(status=task_status, start_time=start_time, duration=duration) + ) + + task_result = QueryTaskResult( + status=task_status, + task_id=task_id, + duration=duration, + ) + + if QueryTaskStatus.FAILED == task_status: + task_result.error_log_path = str(clo_log_path) + + return task_result.dict() + + +def update_query_task_metadata( + sql_adapter: SQL_Adapter, + task_id: int, + kv_pairs: Dict[str, Any], +): + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + if not kv_pairs or len(kv_pairs) == 0: + raise ValueError("No key-value pairs provided to update query task metadata") + + query = f""" + UPDATE {QUERY_TASKS_TABLE_NAME} + SET {', '.join([f'{k}="{v}"' for k, v in kv_pairs.items()])} + WHERE id = {task_id} + """ + db_cursor.execute(query) diff --git a/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py b/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py deleted file mode 100644 index 054b682c2..000000000 --- a/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py +++ /dev/null @@ -1,236 +0,0 @@ -import datetime -import os -import signal -import subprocess -import sys -from contextlib import closing -from pathlib import Path -from typing import Any, Dict - -from celery.app.task import Task -from celery.utils.log import get_task_logger -from clp_py_utils.clp_config import Database, SEARCH_TASKS_TABLE_NAME, StorageEngine -from clp_py_utils.clp_logging import set_logging_level -from clp_py_utils.sql_adapter import SQL_Adapter -from job_orchestration.executor.search.celery import app -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.scheduler_data import SearchTaskResult, SearchTaskStatus - -# Setup logging -logger = get_task_logger(__name__) - - -def update_search_task_metadata( - db_cursor, - task_id: int, - kv_pairs: Dict[str, Any], -): - if not kv_pairs or len(kv_pairs) == 0: - raise ValueError("No key-value pairs provided to update search task metadata") - - query = f""" - UPDATE {SEARCH_TASKS_TABLE_NAME} - SET {', '.join([f'{k}="{v}"' for k, v in kv_pairs.items()])} - WHERE id = {task_id} - """ - db_cursor.execute(query) - - -def make_command( - storage_engine: str, - clp_home: Path, - archives_dir: Path, - archive_id: str, - search_config: SearchConfig, - results_cache_uri: str, - results_collection: str, -): - if StorageEngine.CLP == storage_engine: - command = [str(clp_home / "bin" / "clo"), str(archives_dir / archive_id)] - if search_config.path_filter is not None: - command.append("--file-path") - command.append(search_config.path_filter) - elif StorageEngine.CLP_S == storage_engine: - command = [ - str(clp_home / "bin" / "clp-s"), - "s", - str(archives_dir), - "--archive-id", - archive_id, - ] - else: - raise ValueError(f"Unsupported storage engine {storage_engine}") - - command.append(search_config.query_string) - if search_config.begin_timestamp is not None: - command.append("--tge") - command.append(str(search_config.begin_timestamp)) - if search_config.end_timestamp is not None: - command.append("--tle") - command.append(str(search_config.end_timestamp)) - if search_config.ignore_case: - command.append("--ignore-case") - - if search_config.aggregation_config is not None: - aggregation_config = search_config.aggregation_config - if aggregation_config.do_count_aggregation is not None: - command.append("--count") - if aggregation_config.count_by_time_bucket_size is not None: - command.append("--count-by-time") - command.append(str(aggregation_config.count_by_time_bucket_size)) - - # fmt: off - command.extend(( - "reducer", - "--host", aggregation_config.reducer_host, - "--port", str(aggregation_config.reducer_port), - "--job-id", str(aggregation_config.job_id) - )) - # fmt: on - elif search_config.network_address is not None: - # fmt: off - command.extend(( - "network", - "--host", search_config.network_address[0], - "--port", str(search_config.network_address[1]) - )) - # fmt: on - else: - # fmt: off - command.extend(( - "results-cache", - "--uri", results_cache_uri, - "--collection", results_collection, - "--max-num-results", str(search_config.max_num_results) - )) - # fmt: on - - return command - - -@app.task(bind=True) -def search( - self: Task, - job_id: str, - task_id: int, - search_config_obj: dict, - archive_id: str, - clp_metadata_db_conn_params: dict, - results_cache_uri: str, -) -> Dict[str, Any]: - clp_home = Path(os.getenv("CLP_HOME")) - archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) - clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) - clp_logging_level = str(os.getenv("CLP_LOGGING_LEVEL")) - clp_storage_engine = str(os.getenv("CLP_STORAGE_ENGINE")) - - # Setup logging to file - worker_logs_dir = clp_logs_dir / job_id - worker_logs_dir.mkdir(exist_ok=True, parents=True) - set_logging_level(logger, clp_logging_level) - clo_log_path = worker_logs_dir / f"{task_id}-clo.log" - clo_log_file = open(clo_log_path, "w") - - logger.info(f"Started task for job {job_id}") - - search_config = SearchConfig.parse_obj(search_config_obj) - sql_adapter = SQL_Adapter(Database.parse_obj(clp_metadata_db_conn_params)) - - start_time = datetime.datetime.now() - search_status = SearchTaskStatus.RUNNING - with closing(sql_adapter.create_connection(True)) as db_conn, closing( - db_conn.cursor(dictionary=True) - ) as db_cursor: - try: - search_command = make_command( - storage_engine=clp_storage_engine, - clp_home=clp_home, - archives_dir=archive_directory, - archive_id=archive_id, - search_config=search_config, - results_cache_uri=results_cache_uri, - results_collection=job_id, - ) - except ValueError as e: - error_message = f"Error creating search command: {e}" - logger.error(error_message) - - update_search_task_metadata( - db_cursor, - task_id, - dict(status=SearchTaskStatus.FAILED, duration=0, start_time=start_time), - ) - db_conn.commit() - clo_log_file.write(error_message) - clo_log_file.close() - - return SearchTaskResult( - task_id=task_id, - status=SearchTaskStatus.FAILED, - duration=0, - error_log_path=clo_log_path, - ).dict() - - update_search_task_metadata( - db_cursor, task_id, dict(status=search_status, start_time=start_time) - ) - db_conn.commit() - - logger.info(f'Running: {" ".join(search_command)}') - search_proc = subprocess.Popen( - search_command, - preexec_fn=os.setpgrp, - close_fds=True, - stdout=clo_log_file, - stderr=clo_log_file, - ) - - def sigterm_handler(_signo, _stack_frame): - logger.debug("Entered sigterm handler") - if search_proc.poll() is None: - logger.debug("Trying to kill search process") - # Kill the process group in case the search process also forked - os.killpg(os.getpgid(search_proc.pid), signal.SIGTERM) - os.waitpid(search_proc.pid, 0) - logger.info(f"Cancelling search task.") - # Add 128 to follow convention for exit codes from signals - # https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549 - sys.exit(_signo + 128) - - # Register the function to kill the child process at exit - signal.signal(signal.SIGTERM, sigterm_handler) - - logger.info("Waiting for search to finish") - # communicate is equivalent to wait in this case, but avoids deadlocks if we switch to piping - # stdout/stderr in the future. - search_proc.communicate() - return_code = search_proc.returncode - if 0 != return_code: - search_status = SearchTaskStatus.FAILED - logger.error(f"Failed search task for job {job_id} - return_code={return_code}") - else: - search_status = SearchTaskStatus.SUCCEEDED - logger.info(f"Search task completed for job {job_id}") - - # Close log files - clo_log_file.close() - duration = (datetime.datetime.now() - start_time).total_seconds() - - with closing(sql_adapter.create_connection(True)) as db_conn, closing( - db_conn.cursor(dictionary=True) - ) as db_cursor: - update_search_task_metadata( - db_cursor, task_id, dict(status=search_status, start_time=start_time, duration=duration) - ) - db_conn.commit() - - search_task_result = SearchTaskResult( - status=search_status, - task_id=task_id, - duration=duration, - ) - - if SearchTaskStatus.FAILED == search_status: - search_task_result.error_log_path = clo_log_path - - return search_task_result.dict() diff --git a/components/job-orchestration/job_orchestration/reducer/reducer.py b/components/job-orchestration/job_orchestration/reducer/reducer.py index cee6692df..7a91ab565 100644 --- a/components/job-orchestration/job_orchestration/reducer/reducer.py +++ b/components/job-orchestration/job_orchestration/reducer/reducer.py @@ -55,8 +55,8 @@ def main(argv: List[str]) -> int: # fmt: off reducer_cmd = [ str(clp_home / "bin" / "reducer-server"), - "--scheduler-host", clp_config.search_scheduler.host, - "--scheduler-port", str(clp_config.search_scheduler.port), + "--scheduler-host", clp_config.query_scheduler.host, + "--scheduler-port", str(clp_config.query_scheduler.port), "--mongodb-uri", clp_config.results_cache.get_uri(), "--upsert-interval", str(parsed_args.upsert_interval), "--reducer-host", clp_config.reducer.host, diff --git a/components/job-orchestration/job_orchestration/scheduler/constants.py b/components/job-orchestration/job_orchestration/scheduler/constants.py index adb130b3f..cd85016a3 100644 --- a/components/job-orchestration/job_orchestration/scheduler/constants.py +++ b/components/job-orchestration/job_orchestration/scheduler/constants.py @@ -8,7 +8,7 @@ class QueueName: COMPRESSION = "compression" - SEARCH = "search" + QUERY = "query" class CompressionJobStatus(IntEnum): @@ -31,8 +31,8 @@ class CompressionTaskStatus(IntEnum): # When adding new states always add them to the end of this enum -# and make necessary changes in the UI, Search Scheduler, and Reducer -class SearchJobStatus(IntEnum): +# and make necessary changes in the UI, Query Scheduler, and Reducer +class QueryJobStatus(IntEnum): PENDING = 0 RUNNING = auto() SUCCEEDED = auto() @@ -41,8 +41,8 @@ class SearchJobStatus(IntEnum): CANCELLED = auto() @staticmethod - def from_str(label: str) -> SearchJobStatus: - return SearchJobStatus[label.upper()] + def from_str(label: str) -> QueryJobStatus: + return QueryJobStatus[label.upper()] def __str__(self) -> str: return str(self.value) @@ -51,7 +51,7 @@ def to_str(self) -> str: return str(self.name) -class SearchTaskStatus(IntEnum): +class QueryTaskStatus(IntEnum): PENDING = 0 RUNNING = auto() SUCCEEDED = auto() @@ -59,8 +59,20 @@ class SearchTaskStatus(IntEnum): CANCELLED = auto() @staticmethod - def from_str(label: str) -> SearchTaskStatus: - return SearchTaskStatus[label.upper()] + def from_str(label: str) -> QueryTaskStatus: + return QueryTaskStatus[label.upper()] + + def __str__(self) -> str: + return str(self.value) + + def to_str(self) -> str: + return str(self.name) + + +class QueryJobType(IntEnum): + SEARCH_OR_AGGREGATION = 0 + EXTRACT_IR = auto() + EXTRACT_JSON = auto() def __str__(self) -> str: return str(self.value) diff --git a/components/job-orchestration/job_orchestration/scheduler/job_config.py b/components/job-orchestration/job_orchestration/scheduler/job_config.py index 93d4ede4e..7cf8b2324 100644 --- a/components/job-orchestration/job_orchestration/scheduler/job_config.py +++ b/components/job-orchestration/job_orchestration/scheduler/job_config.py @@ -39,7 +39,22 @@ class AggregationConfig(BaseModel): count_by_time_bucket_size: typing.Optional[int] = None # Milliseconds -class SearchConfig(BaseModel): +class QueryJobConfig(BaseModel): ... + + +class ExtractIrJobConfig(QueryJobConfig): + orig_file_id: str + msg_ix: int + file_split_id: typing.Optional[str] = None + target_uncompressed_size: typing.Optional[int] = None + + +class ExtractJsonJobConfig(QueryJobConfig): + archive_id: str + target_chunk_size: typing.Optional[int] = None + + +class SearchJobConfig(QueryJobConfig): query_string: str max_num_results: int tags: typing.Optional[typing.List[str]] = None diff --git a/components/job-orchestration/job_orchestration/scheduler/search/__init__.py b/components/job-orchestration/job_orchestration/scheduler/query/__init__.py similarity index 100% rename from components/job-orchestration/job_orchestration/scheduler/search/__init__.py rename to components/job-orchestration/job_orchestration/scheduler/query/__init__.py diff --git a/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py new file mode 100644 index 000000000..a9c60f380 --- /dev/null +++ b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py @@ -0,0 +1,1160 @@ +""" +A scheduler for scheduling query jobs in the CLP package. + +NOTE: This scheduler currently only has partial handling for failures of the database. Specifically, +in the event that the database is unreachable, the scheduler will continue running as if reads from +the database return no records and writes to the database always fail. Failed writes are currently +silently ignored, in which case the state of the database won't match the scheduler's internal +state. If the database comes back up, the scheduler will eventually reconnect to it and reads/writes +will function as normal again. However, the mismatched state may lead to unexpected behaviour like +jobs seemingly being stuck in the "RUNNING" state or jobs being repeated, which in turn will create +duplicated search results in the results cache, possibly long after the results had already been +cleared from the cache. Unfortunately, these effects will require manual intervention to clean-up. +TODO Address this limitation. +""" + +from __future__ import annotations + +import argparse +import asyncio +import contextlib +import datetime +import logging +import os +import pathlib +import sys +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +import celery +import msgpack +import pymongo +from clp_py_utils.clp_config import ( + CLP_METADATA_TABLE_PREFIX, + CLPConfig, + QUERY_JOBS_TABLE_NAME, + QUERY_TASKS_TABLE_NAME, +) +from clp_py_utils.clp_logging import get_logger, get_logging_formatter, set_logging_level +from clp_py_utils.core import read_yaml_config_file +from clp_py_utils.decorators import exception_default_value +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.extract_stream_task import extract_stream +from job_orchestration.executor.query.fs_search_task import search +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType, QueryTaskStatus +from job_orchestration.scheduler.job_config import ( + ExtractIrJobConfig, + ExtractJsonJobConfig, + SearchJobConfig, +) +from job_orchestration.scheduler.query.reducer_handler import ( + handle_reducer_connection, + ReducerHandlerMessage, + ReducerHandlerMessageQueues, + ReducerHandlerMessageType, +) +from job_orchestration.scheduler.scheduler_data import ( + ExtractIrJob, + ExtractJsonJob, + InternalJobState, + QueryJob, + QueryTaskResult, + SearchJob, +) +from pydantic import ValidationError + +# Setup logging +logger = get_logger("search-job-handler") + +# Dictionary of active jobs indexed by job id +active_jobs: Dict[str, QueryJob] = {} + +# Dictionary that maps IDs of file splits being extracted to IDs of jobs waiting for them +active_file_split_ir_extractions: Dict[str, List[str]] = {} + +# Dictionary that maps IDs of clp-s archives being extracted to IDs of jobs waiting for them +active_archive_json_extractions: Dict[str, List[str]] = {} + +reducer_connection_queue: Optional[asyncio.Queue] = None + + +class StreamExtractionHandle(ABC): + def __init__(self, job_id: str): + self._job_id = job_id + self._archive_id: Optional[str] = None + + def get_archive_id(self) -> Optional[str]: + return self._archive_id + + @abstractmethod + def get_stream_id(self) -> str: ... + + @abstractmethod + def is_stream_extraction_active(self) -> bool: ... + + @abstractmethod + def is_stream_extracted(self, results_cache_uri: str, stream_collection_name: str) -> bool: ... + + @abstractmethod + def mark_job_as_waiting(self) -> None: ... + + @abstractmethod + def create_stream_extraction_job(self) -> QueryJob: ... + + +class IrExtractionHandle(StreamExtractionHandle): + def __init__(self, job_id: str, job_config: Dict[str, Any], db_conn): + super().__init__(job_id) + self.__job_config = ExtractIrJobConfig.parse_obj(job_config) + self._archive_id, self.__file_split_id = get_archive_and_file_split_ids_for_ir_extraction( + db_conn, self.__job_config + ) + if self._archive_id is None: + raise ValueError("Job parameters don't resolve to an existing archive") + + self.__job_config.file_split_id = self.__file_split_id + + def get_stream_id(self) -> str: + return self.__file_split_id + + def is_stream_extraction_active(self) -> bool: + return self.__file_split_id in active_file_split_ir_extractions + + def is_stream_extracted(self, results_cache_uri: str, stream_collection_name: str) -> bool: + return document_exists( + results_cache_uri, stream_collection_name, "file_split_id", self.__file_split_id + ) + + def mark_job_as_waiting(self) -> None: + global active_file_split_ir_extractions + file_split_id = self.__file_split_id + if file_split_id not in active_file_split_ir_extractions: + active_file_split_ir_extractions[file_split_id] = [] + active_file_split_ir_extractions[file_split_id].append(self._job_id) + + def create_stream_extraction_job(self) -> QueryJob: + logger.info( + f"Creating IR extraction job {self._job_id} for file_split: {self.__file_split_id}" + ) + return ExtractIrJob( + id=self._job_id, + extract_ir_config=self.__job_config, + state=InternalJobState.WAITING_FOR_DISPATCH, + ) + + +class JsonExtractionHandle(StreamExtractionHandle): + def __init__(self, job_id: str, job_config: Dict[str, Any], db_conn): + super().__init__(job_id) + self.__job_config = ExtractJsonJobConfig.parse_obj(job_config) + self._archive_id = self.__job_config.archive_id + if not archive_exists(db_conn, self._archive_id): + raise ValueError(f"Archive {self._archive_id} doesn't exist") + + def get_stream_id(self) -> str: + return self._archive_id + + def is_stream_extraction_active(self) -> bool: + return self._archive_id in active_archive_json_extractions + + def is_stream_extracted(self, results_cache_uri: str, stream_collection_name: str) -> bool: + return document_exists( + results_cache_uri, stream_collection_name, "orig_file_id", self._archive_id + ) + + def mark_job_as_waiting(self) -> None: + global active_archive_json_extractions + archive_id = self._archive_id + if archive_id not in active_archive_json_extractions: + active_archive_json_extractions[archive_id] = [] + active_archive_json_extractions[archive_id].append(self._job_id) + + def create_stream_extraction_job(self) -> QueryJob: + logger.info(f"Creating json extraction job {self._job_id} on archive: {self._archive_id}") + return ExtractJsonJob( + id=self._job_id, + extract_json_config=self.__job_config, + state=InternalJobState.WAITING_FOR_DISPATCH, + ) + + +def document_exists(mongodb_uri, collection_name, field, value): + with pymongo.MongoClient(mongodb_uri) as mongo_client: + collection = mongo_client.get_default_database()[collection_name] + return 0 != collection.count_documents({field: value}) + + +def cancel_job_except_reducer(job: SearchJob): + """ + Cancels the job apart from releasing the reducer since that requires an async call. + NOTE: By keeping this method synchronous, the caller can cancel most of the job atomically, + making it easier to avoid using locks in concurrent tasks. + :param job: + """ + + if InternalJobState.RUNNING == job.state: + job.current_sub_job_async_task_result.revoke(terminate=True) + try: + job.current_sub_job_async_task_result.get() + except Exception: + pass + elif InternalJobState.WAITING_FOR_REDUCER == job.state: + job.reducer_acquisition_task.cancel() + + +async def release_reducer_for_job(job: SearchJob): + """ + Releases the reducer assigned to the given job + :param job: + """ + + if job.reducer_handler_msg_queues is not None: + # Signal the reducer to cancel the job + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + +@exception_default_value(default=[]) +def fetch_new_query_jobs(db_conn) -> list: + """ + Fetches query jobs with status=PENDING from the database. + :param db_conn: + :return: The pending query jobs on success. An empty list if an exception occurs while + interacting with the database. + """ + with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: + db_cursor.execute( + f""" + SELECT {QUERY_JOBS_TABLE_NAME}.id as job_id, + {QUERY_JOBS_TABLE_NAME}.job_config, + {QUERY_JOBS_TABLE_NAME}.type + FROM {QUERY_JOBS_TABLE_NAME} + WHERE {QUERY_JOBS_TABLE_NAME}.status={QueryJobStatus.PENDING} + """ + ) + return db_cursor.fetchall() + + +@exception_default_value(default=[]) +def fetch_cancelling_search_jobs(db_conn) -> list: + """ + Fetches search jobs with status=CANCELLING from the database. + :param db_conn: + :return: The cancelling search jobs on success. An empty list if an exception occurs while + interacting with the database. + """ + with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: + db_cursor.execute( + f""" + SELECT {QUERY_JOBS_TABLE_NAME}.id as job_id + FROM {QUERY_JOBS_TABLE_NAME} + WHERE {QUERY_JOBS_TABLE_NAME}.status={QueryJobStatus.CANCELLING} + AND {QUERY_JOBS_TABLE_NAME}.type={QueryJobType.SEARCH_OR_AGGREGATION} + """ + ) + return db_cursor.fetchall() + + +@exception_default_value(default=False) +def set_job_or_task_status( + db_conn, + table_name: str, + job_id: str, + status: QueryJobStatus | QueryTaskStatus, + prev_status: Optional[QueryJobStatus | QueryTaskStatus] = None, + **kwargs, +) -> bool: + """ + Sets the status of the job or the tasks identified by `job_id` to `status`. If `prev_status` is + specified, the update is conditional on the job/task's current status matching `prev_status`. If + `kwargs` are specified, the fields identified by the args are also updated. + :param db_conn: + :param table_name: + :param job_id: + :param status: + :param prev_status: + :param kwargs: + :return: True on success, False if the update fails or an exception occurs while interacting + with the database. + """ + field_set_expressions = [f"status={status}"] + if QUERY_JOBS_TABLE_NAME == table_name: + id_col_name = "id" + field_set_expressions.extend([f'{k}="{v}"' for k, v in kwargs.items()]) + elif QUERY_TASKS_TABLE_NAME == table_name: + id_col_name = "job_id" + field_set_expressions.extend([f"{k}={v}" for k, v in kwargs.items()]) + else: + raise ValueError(f"Unsupported table name {table_name}") + update = ( + f'UPDATE {table_name} SET {", ".join(field_set_expressions)} WHERE {id_col_name}={job_id}' + ) + + if prev_status is not None: + update += f" AND status={prev_status}" + + with contextlib.closing(db_conn.cursor()) as cursor: + cursor.execute(update) + db_conn.commit() + rval = cursor.rowcount != 0 + return rval + + +async def handle_cancelling_search_jobs(db_conn_pool) -> None: + global active_jobs + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + cancelling_jobs = fetch_cancelling_search_jobs(db_conn) + + for cancelling_job in cancelling_jobs: + job_id = str(cancelling_job["job_id"]) + if job_id in active_jobs: + job = active_jobs.pop(job_id) + cancel_job_except_reducer(job) + # Perform any async tasks last so that it's easier to reason about synchronization + # issues between concurrent tasks + await release_reducer_for_job(job) + else: + continue + + set_job_or_task_status( + db_conn, + QUERY_TASKS_TABLE_NAME, + job_id, + QueryTaskStatus.CANCELLED, + QueryTaskStatus.PENDING, + duration=0, + ) + + set_job_or_task_status( + db_conn, + QUERY_TASKS_TABLE_NAME, + job_id, + QueryTaskStatus.CANCELLED, + QueryTaskStatus.RUNNING, + duration="TIMESTAMPDIFF(MICROSECOND, start_time, NOW())/1000000.0", + ) + + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.CANCELLED, + QueryJobStatus.CANCELLING, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + logger.info(f"Cancelled job {job_id}.") + else: + logger.error(f"Failed to cancel job {job_id}.") + + +def insert_query_tasks_into_db(db_conn, job_id, archive_ids: List[str]) -> List[int]: + task_ids = [] + with contextlib.closing(db_conn.cursor()) as cursor: + for archive_id in archive_ids: + cursor.execute( + f""" + INSERT INTO {QUERY_TASKS_TABLE_NAME} + (job_id, archive_id) + VALUES({job_id}, '{archive_id}') + """ + ) + task_ids.append(cursor.lastrowid) + db_conn.commit() + return task_ids + + +@exception_default_value(default=[]) +def get_archives_for_search( + db_conn, + search_config: SearchJobConfig, +): + query = f"""SELECT id as archive_id, end_timestamp + FROM {CLP_METADATA_TABLE_PREFIX}archives + """ + filter_clauses = [] + if search_config.end_timestamp is not None: + filter_clauses.append(f"begin_timestamp <= {search_config.end_timestamp}") + if search_config.begin_timestamp is not None: + filter_clauses.append(f"end_timestamp >= {search_config.begin_timestamp}") + if search_config.tags is not None: + filter_clauses.append( + f"id IN (SELECT archive_id FROM {CLP_METADATA_TABLE_PREFIX}archive_tags WHERE " + f"tag_id IN (SELECT tag_id FROM {CLP_METADATA_TABLE_PREFIX}tags WHERE tag_name IN " + f"(%s)))" % ", ".join(["%s" for _ in search_config.tags]) + ) + if len(filter_clauses) > 0: + query += " WHERE " + " AND ".join(filter_clauses) + query += " ORDER BY end_timestamp DESC" + + with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: + if search_config.tags is not None: + cursor.execute(query, tuple(search_config.tags)) + else: + cursor.execute(query) + archives_for_search = list(cursor.fetchall()) + return archives_for_search + + +def get_archive_and_file_split_ids_for_ir_extraction( + db_conn, + extract_ir_config: ExtractIrJobConfig, +) -> Tuple[Optional[str], Optional[str]]: + orig_file_id = extract_ir_config.orig_file_id + msg_ix = extract_ir_config.msg_ix + + results = get_archive_and_file_split_ids(db_conn, orig_file_id, msg_ix) + if len(results) == 0: + logger.error(f"No matching file splits for orig_file_id={orig_file_id}, msg_ix={msg_ix}") + return None, None + elif len(results) > 1: + logger.error(f"Multiple file splits found for orig_file_id={orig_file_id}, msg_ix={msg_ix}") + for result in results: + logger.error(f"{result['archive_id']}:{result['id']}") + return None, None + + return results[0]["archive_id"], results[0]["file_split_id"] + + +@exception_default_value(default=[]) +def get_archive_and_file_split_ids( + db_conn, + orig_file_id: str, + msg_ix: int, +): + """ + Fetches the IDs of the file split and the archive containing the file split based on the + following criteria: + 1. The file split's original file id = `orig_file_id` + 2. The file split includes the message with index = `msg_ix` + :param db_conn: + :param orig_file_id: Original file id of the split + :param msg_ix: Index of the message that the file split must include + :return: A list of (archive id, file split id) on success. An empty list if + an exception occurs while interacting with the database. + """ + + query = f"""SELECT archive_id, id as file_split_id + FROM {CLP_METADATA_TABLE_PREFIX}files WHERE + orig_file_id = '{orig_file_id}' AND + begin_message_ix <= {msg_ix} AND + (begin_message_ix + num_messages) > {msg_ix} + """ + + with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: + cursor.execute(query) + results = list(cursor.fetchall()) + return results + + +@exception_default_value(default=False) +def archive_exists( + db_conn, + archive_id: str, +) -> bool: + query = f"""SELECT 1 + FROM {CLP_METADATA_TABLE_PREFIX}archives WHERE + id = %s + """ + with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: + cursor.execute(query, (archive_id,)) + if cursor.fetchone(): + return True + + return False + + +def get_task_group_for_job( + archive_ids: List[str], + task_ids: List[int], + job: QueryJob, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, +): + job_config = job.get_config().dict() + job_type = job.get_type() + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + return celery.group( + search.s( + job_id=job.id, + archive_id=archive_ids[i], + task_id=task_ids[i], + job_config=job_config, + clp_metadata_db_conn_params=clp_metadata_db_conn_params, + results_cache_uri=results_cache_uri, + ) + for i in range(len(archive_ids)) + ) + elif job_type in (QueryJobType.EXTRACT_JSON, QueryJobType.EXTRACT_IR): + return celery.group( + extract_stream.s( + job_id=job.id, + archive_id=archive_ids[i], + task_id=task_ids[i], + job_config=job_config, + clp_metadata_db_conn_params=clp_metadata_db_conn_params, + results_cache_uri=results_cache_uri, + ) + for i in range(len(archive_ids)) + ) + else: + error_msg = f"Unexpected job type: {job_type}" + logger.error(error_msg) + raise NotImplementedError(error_msg) + + +def dispatch_query_job( + db_conn, + job: QueryJob, + archive_ids: List[str], + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, +) -> None: + global active_jobs + task_ids = insert_query_tasks_into_db(db_conn, job.id, archive_ids) + + task_group = get_task_group_for_job( + archive_ids, + task_ids, + job, + clp_metadata_db_conn_params, + results_cache_uri, + ) + job.current_sub_job_async_task_result = task_group.apply_async() + job.state = InternalJobState.RUNNING + + +async def acquire_reducer_for_job(job: SearchJob): + reducer_host: Optional[str] = None + reducer_port: Optional[int] = None + reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] = None + while True: + reducer_host, reducer_port, reducer_handler_msg_queues = ( + await reducer_connection_queue.get() + ) + """ + Below, the task can either be cancelled before sending the job config to the reducer or + before the reducer acknowledges the job. If the task is cancelled before we send the job + config to the reducer, then we have two options: + + 1. Put the reducer's connection info back in the queue for another job to pick up. + 2. Tell the reducer to restart its job handling loop. + + If the task is cancelled after we've sent the job config to the reducer, then we have to + use option (2), so we use option (2) in both cases. + """ + try: + msg = ReducerHandlerMessage( + ReducerHandlerMessageType.AGGREGATION_CONFIG, job.search_config.aggregation_config + ) + await reducer_handler_msg_queues.put_to_handler(msg) + + msg = await reducer_handler_msg_queues.get_from_handler() + if msg.msg_type == ReducerHandlerMessageType.SUCCESS: + break + elif msg.msg_type != ReducerHandlerMessageType.FAILURE: + error_msg = f"Unexpected msg_type: {msg.msg_type.name}" + raise NotImplementedError(error_msg) + except asyncio.CancelledError: + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await reducer_handler_msg_queues.put_to_handler(msg) + raise + + job.reducer_handler_msg_queues = reducer_handler_msg_queues + job.search_config.aggregation_config.reducer_host = reducer_host + job.search_config.aggregation_config.reducer_port = reducer_port + job.state = InternalJobState.WAITING_FOR_DISPATCH + job.reducer_acquisition_task = None + + logger.info(f"Got reducer for job {job.id} at {reducer_host}:{reducer_port}") + + +def dispatch_job_and_update_db( + db_conn, + new_job: QueryJob, + target_archives: List[str], + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + num_tasks: int, +) -> None: + dispatch_query_job( + db_conn, new_job, target_archives, clp_metadata_db_conn_params, results_cache_uri + ) + start_time = datetime.datetime.now() + new_job.start_time = start_time + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + new_job.id, + QueryJobStatus.RUNNING, + QueryJobStatus.PENDING, + start_time=start_time, + num_tasks=num_tasks, + ) + + +def handle_pending_query_jobs( + db_conn_pool, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + stream_collection_name: str, + num_archives_to_search_per_sub_job: int, +) -> List[asyncio.Task]: + global active_jobs + + reducer_acquisition_tasks = [] + pending_search_jobs = [ + job + for job in active_jobs.values() + if InternalJobState.WAITING_FOR_DISPATCH == job.state + and job.get_type() == QueryJobType.SEARCH_OR_AGGREGATION + ] + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + for job in fetch_new_query_jobs(db_conn): + job_id = str(job["job_id"]) + job_type = job["type"] + job_config = msgpack.unpackb(job["job_config"]) + + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + # Avoid double-dispatch when a job is WAITING_FOR_REDUCER + if job_id in active_jobs: + continue + + search_config = SearchJobConfig.parse_obj(job_config) + archives_for_search = get_archives_for_search(db_conn, search_config) + if len(archives_for_search) == 0: + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.SUCCEEDED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.info(f"No matching archives, skipping job {job_id}.") + continue + + new_search_job = SearchJob( + id=job_id, + search_config=search_config, + state=InternalJobState.WAITING_FOR_DISPATCH, + num_archives_to_search=len(archives_for_search), + num_archives_searched=0, + remaining_archives_for_search=archives_for_search, + ) + + if search_config.aggregation_config is not None: + new_search_job.search_config.aggregation_config.job_id = int(job_id) + new_search_job.state = InternalJobState.WAITING_FOR_REDUCER + new_search_job.reducer_acquisition_task = asyncio.create_task( + acquire_reducer_for_job(new_search_job) + ) + reducer_acquisition_tasks.append(new_search_job.reducer_acquisition_task) + else: + pending_search_jobs.append(new_search_job) + active_jobs[job_id] = new_search_job + + elif job_type in (QueryJobType.EXTRACT_IR, QueryJobType.EXTRACT_JSON): + job_handle: StreamExtractionHandle + try: + if QueryJobType.EXTRACT_IR == job_type: + job_handle = IrExtractionHandle(job_id, job_config, db_conn) + else: + job_handle = JsonExtractionHandle(job_id, job_config, db_conn) + except ValueError: + logger.exception("Failed to initialize extraction job handle") + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.FAILED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.error(f"Failed to set job {job_id} as failed") + continue + + # NOTE: The following two if blocks for `is_stream_extraction_active` and + # `is_stream_extracted` should not be reordered. + # + # The logic below works as follows: + # 1. It checks if a stream is already being extracted + # (`is_stream_extraction_active`) and if so, it marks the new job as waiting for + # the old job to finish. + # 2. Otherwise, it checks if a stream has already been extracted + # (`is_stream_extracted`) and if so, it marks the new job as complete. + # 3. Otherwise, it creates a new stream extraction job. + # + # `is_stream_extracted` only checks if a single stream has been extracted rather + # than whether all required streams have been extracted. This means that we can't + # use it to check if the old job is complete; instead, we need to employ the + # aforementioned logic. + + # Check if the required streams are currently being extracted; if so, add the job ID + # to the list of jobs waiting for it. + if job_handle.is_stream_extraction_active(): + job_handle.mark_job_as_waiting() + logger.info( + f"Stream {job_handle.get_stream_id()} is already being extracted," + f" so mark job {job_id} as running." + ) + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.RUNNING, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + ): + logger.error(f"Failed to set job {job_id} as running") + continue + + # Check if a required stream file has already been extracted + if job_handle.is_stream_extracted(results_cache_uri, stream_collection_name): + logger.info( + f"Stream {job_handle.get_stream_id()} already extracted," + f" so mark job {job_id} as succeeded." + ) + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.SUCCEEDED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.error(f"Failed to set job {job_id} as succeeded") + continue + + new_stream_extraction_job = job_handle.create_stream_extraction_job() + archive_id = job_handle.get_archive_id() + dispatch_job_and_update_db( + db_conn, + new_stream_extraction_job, + [archive_id], + clp_metadata_db_conn_params, + results_cache_uri, + 1, + ) + + job_handle.mark_job_as_waiting() + active_jobs[job_id] = new_stream_extraction_job + logger.info(f"Dispatched stream extraction job {job_id} for archive: {archive_id}") + + else: + # NOTE: We're skipping the job for this iteration, but its status will remain + # unchanged. So this log will print again in the next iteration unless the user + # cancels the job. + logger.error(f"Unexpected job type: {job_type}, skipping job {job_id}") + continue + + for job in pending_search_jobs: + job_id = job.id + if ( + job.search_config.network_address is None + and len(job.remaining_archives_for_search) > num_archives_to_search_per_sub_job + ): + archives_for_search = job.remaining_archives_for_search[ + :num_archives_to_search_per_sub_job + ] + job.remaining_archives_for_search = job.remaining_archives_for_search[ + num_archives_to_search_per_sub_job: + ] + else: + archives_for_search = job.remaining_archives_for_search + job.remaining_archives_for_search = [] + + archive_ids_for_search = [archive["archive_id"] for archive in archives_for_search] + + dispatch_job_and_update_db( + db_conn, + job, + archive_ids_for_search, + clp_metadata_db_conn_params, + results_cache_uri, + job.num_archives_to_search, + ) + logger.info( + f"Dispatched job {job_id} with {len(archive_ids_for_search)} archives to search." + ) + + return reducer_acquisition_tasks + + +def try_getting_task_result(async_task_result): + if not async_task_result.ready(): + return None + return async_task_result.get() + + +def found_max_num_latest_results( + results_cache_uri: str, + job_id: str, + max_num_results: int, + max_timestamp_in_remaining_archives: int, +) -> bool: + with pymongo.MongoClient(results_cache_uri) as results_cache_client: + results_cache_collection = results_cache_client.get_default_database()[job_id] + results_count = results_cache_collection.count_documents({}) + if results_count < max_num_results: + return False + + results = list( + results_cache_collection.find( + projection=["timestamp"], + sort=[("timestamp", pymongo.DESCENDING)], + limit=max_num_results, + ) + .sort("timestamp", pymongo.ASCENDING) + .limit(1) + ) + min_timestamp_in_top_results = 0 if len(results) == 0 else results[0]["timestamp"] + return max_timestamp_in_remaining_archives <= min_timestamp_in_top_results + + +async def handle_finished_search_job( + db_conn, job: SearchJob, task_results: Optional[Any], results_cache_uri: str +) -> None: + global active_jobs + + job_id = job.id + is_reducer_job = job.reducer_handler_msg_queues is not None + new_job_status = QueryJobStatus.RUNNING + for task_result_obj in task_results: + task_result = QueryTaskResult.parse_obj(task_result_obj) + task_id = task_result.task_id + task_status = task_result.status + if not task_status == QueryTaskStatus.SUCCEEDED: + new_job_status = QueryJobStatus.FAILED + logger.error( + f"Search task job-{job_id}-task-{task_id} failed. " + f"Check {task_result.error_log_path} for details." + ) + else: + job.num_archives_searched += 1 + logger.info( + f"Search task job-{job_id}-task-{task_id} succeeded in " + f"{task_result.duration} second(s)." + ) + + if new_job_status != QueryJobStatus.FAILED: + max_num_results = job.search_config.max_num_results + # Check if we've searched all archives + if len(job.remaining_archives_for_search) == 0: + new_job_status = QueryJobStatus.SUCCEEDED + # Check if we've reached max results + elif False == is_reducer_job and max_num_results > 0: + if found_max_num_latest_results( + results_cache_uri, + job_id, + max_num_results, + job.remaining_archives_for_search[0]["end_timestamp"], + ): + new_job_status = QueryJobStatus.SUCCEEDED + if new_job_status == QueryJobStatus.RUNNING: + job.current_sub_job_async_task_result = None + job.state = InternalJobState.WAITING_FOR_DISPATCH + logger.info(f"Job {job_id} waiting for more archives to search.") + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.RUNNING, + QueryJobStatus.RUNNING, + num_tasks_completed=job.num_archives_searched, + ) + return + + reducer_failed = False + if is_reducer_job: + # Notify reducer that it should have received all results + msg = ReducerHandlerMessage(ReducerHandlerMessageType.SUCCESS) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + msg = await job.reducer_handler_msg_queues.get_from_handler() + if ReducerHandlerMessageType.FAILURE == msg.msg_type: + reducer_failed = True + new_job_status = QueryJobStatus.FAILED + elif ReducerHandlerMessageType.SUCCESS != msg.msg_type: + error_msg = f"Unexpected msg_type: {msg.msg_type.name}" + raise NotImplementedError(error_msg) + + # We set the status regardless of the job's previous status to handle the case where the + # job is cancelled (status = CANCELLING) while we're in this method. + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + new_job_status, + num_tasks_completed=job.num_archives_searched, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + if new_job_status == QueryJobStatus.SUCCEEDED: + logger.info(f"Completed job {job_id}.") + elif reducer_failed: + logger.error(f"Completed job {job_id} with failing reducer.") + else: + logger.info(f"Completed job {job_id} with failing tasks.") + del active_jobs[job_id] + + +async def handle_finished_stream_extraction_job( + db_conn, job: QueryJob, task_results: List[Any] +) -> None: + global active_jobs + global active_archive_json_extractions + global active_file_split_ir_extractions + + job_id = job.id + new_job_status = QueryJobStatus.SUCCEEDED + + num_tasks = len(task_results) + if 1 != num_tasks: + logger.error( + f"Unexpected number of tasks for extraction job {job_id}. " + f"Expected 1, got {num_tasks}." + ) + new_job_status = QueryJobStatus.FAILED + else: + task_result = QueryTaskResult.parse_obj(task_results[0]) + task_id = task_result.task_id + if not QueryJobStatus.SUCCEEDED == task_result.status: + logger.error( + f"Extraction task job-{job_id}-task-{task_id} failed. " + f"Check {task_result.error_log_path} for details." + ) + new_job_status = QueryJobStatus.FAILED + else: + logger.info( + f"Extraction task job-{job_id}-task-{task_id} succeeded in " + f"{task_result.duration} second(s)." + ) + + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + new_job_status, + QueryJobStatus.RUNNING, + num_tasks_completed=num_tasks, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + if new_job_status == QueryJobStatus.SUCCEEDED: + logger.info(f"Completed stream extraction job {job_id}.") + else: + logger.info(f"Completed stream extraction job {job_id} with failing tasks.") + + waiting_jobs: List[str] + if QueryJobType.EXTRACT_IR == job.get_type(): + extract_ir_config: ExtractIrJobConfig = job.get_config() + waiting_jobs = active_file_split_ir_extractions.pop(extract_ir_config.file_split_id) + else: + extract_json_config: ExtractJsonJobConfig = job.get_config() + waiting_jobs = active_archive_json_extractions.pop(extract_json_config.archive_id) + + waiting_jobs.remove(job_id) + for waiting_job in waiting_jobs: + logger.info(f"Setting status to {new_job_status.to_str()} for waiting jobs: {waiting_job}.") + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + waiting_job, + new_job_status, + QueryJobStatus.RUNNING, + num_tasks_completed=0, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ) + + del active_jobs[job_id] + + +async def check_job_status_and_update_db(db_conn_pool, results_cache_uri): + global active_jobs + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + for job_id in [ + id for id, job in active_jobs.items() if InternalJobState.RUNNING == job.state + ]: + job = active_jobs[job_id] + try: + returned_results = try_getting_task_result(job.current_sub_job_async_task_result) + except Exception as e: + logger.error(f"Job `{job_id}` failed: {e}.") + # Clean up + if QueryJobType.SEARCH_OR_AGGREGATION == job.get_type(): + if job.reducer_handler_msg_queues is not None: + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + del active_jobs[job_id] + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.FAILED, + QueryJobStatus.RUNNING, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ) + continue + + if returned_results is None: + continue + job_type = job.get_type() + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + search_job: SearchJob = job + await handle_finished_search_job( + db_conn, search_job, returned_results, results_cache_uri + ) + elif job_type in (QueryJobType.EXTRACT_JSON, QueryJobType.EXTRACT_IR): + await handle_finished_stream_extraction_job(db_conn, job, returned_results) + else: + logger.error(f"Unexpected job type: {job_type}, skipping job {job_id}") + + +async def handle_job_updates(db_conn_pool, results_cache_uri: str, jobs_poll_delay: float): + while True: + await handle_cancelling_search_jobs(db_conn_pool) + await check_job_status_and_update_db(db_conn_pool, results_cache_uri) + await asyncio.sleep(jobs_poll_delay) + + +async def handle_jobs( + db_conn_pool, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + stream_collection_name: str, + jobs_poll_delay: float, + num_archives_to_search_per_sub_job: int, +) -> None: + handle_updating_task = asyncio.create_task( + handle_job_updates(db_conn_pool, results_cache_uri, jobs_poll_delay) + ) + + tasks = [handle_updating_task] + while True: + reducer_acquisition_tasks = handle_pending_query_jobs( + db_conn_pool, + clp_metadata_db_conn_params, + results_cache_uri, + stream_collection_name, + num_archives_to_search_per_sub_job, + ) + if 0 == len(reducer_acquisition_tasks): + tasks.append(asyncio.create_task(asyncio.sleep(jobs_poll_delay))) + else: + tasks.extend(reducer_acquisition_tasks) + + done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + if handle_updating_task in done: + logger.error("handle_job_updates completed unexpectedly.") + try: + handle_updating_task.result() + except Exception: + logger.exception("handle_job_updates failed.") + return + tasks = list(pending) + + +async def main(argv: List[str]) -> int: + global reducer_connection_queue + + args_parser = argparse.ArgumentParser(description="Wait for and run query jobs.") + args_parser.add_argument("--config", "-c", required=True, help="CLP configuration file.") + + parsed_args = args_parser.parse_args(argv[1:]) + + # Setup logging to file + log_file = Path(os.getenv("CLP_LOGS_DIR")) / "query_scheduler.log" + logging_file_handler = logging.FileHandler(filename=log_file, encoding="utf-8") + logging_file_handler.setFormatter(get_logging_formatter()) + logger.addHandler(logging_file_handler) + + # Update logging level based on config + set_logging_level(logger, os.getenv("CLP_LOGGING_LEVEL")) + + # Load configuration + config_path = pathlib.Path(parsed_args.config) + try: + clp_config = CLPConfig.parse_obj(read_yaml_config_file(config_path)) + except ValidationError as err: + logger.error(err) + return -1 + except Exception as ex: + logger.error(ex) + return -1 + + reducer_connection_queue = asyncio.Queue(32) + + sql_adapter = SQL_Adapter(clp_config.database) + + logger.debug(f"Job polling interval {clp_config.query_scheduler.jobs_poll_delay} seconds.") + try: + reducer_handler = await asyncio.start_server( + lambda reader, writer: handle_reducer_connection( + reader, writer, reducer_connection_queue + ), + clp_config.query_scheduler.host, + clp_config.query_scheduler.port, + ) + db_conn_pool = sql_adapter.create_connection_pool( + logger=logger, pool_size=2, disable_localhost_socket_connection=True + ) + + if False == db_conn_pool.alive(): + logger.error( + f"Failed to connect to archive database " + f"{clp_config.database.host}:{clp_config.database.port}." + ) + return -1 + + logger.info( + f"Connected to archive database" + f" {clp_config.database.host}:{clp_config.database.port}." + ) + logger.info("Query scheduler started.") + batch_size = clp_config.query_scheduler.num_archives_to_search_per_sub_job + job_handler = asyncio.create_task( + handle_jobs( + db_conn_pool=db_conn_pool, + clp_metadata_db_conn_params=clp_config.database.get_clp_connection_params_and_type( + True + ), + results_cache_uri=clp_config.results_cache.get_uri(), + stream_collection_name=clp_config.results_cache.stream_collection_name, + jobs_poll_delay=clp_config.query_scheduler.jobs_poll_delay, + num_archives_to_search_per_sub_job=batch_size, + ) + ) + reducer_handler = asyncio.create_task(reducer_handler.serve_forever()) + done, pending = await asyncio.wait( + [job_handler, reducer_handler], return_when=asyncio.FIRST_COMPLETED + ) + if reducer_handler in done: + logger.error("reducer_handler completed unexpectedly.") + try: + reducer_handler.result() + except Exception: + logger.exception("reducer_handler failed.") + if job_handler in done: + logger.error("job_handler completed unexpectedly.") + try: + job_handler.result() + except Exception: + logger.exception("job_handler failed.") + except Exception: + logger.exception(f"Uncaught exception in job handling loop.") + + return 0 + + +if "__main__" == __name__: + sys.exit(asyncio.run(main(sys.argv))) diff --git a/components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py b/components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py similarity index 96% rename from components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py rename to components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py index f25d8afce..5d2ae538b 100644 --- a/components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py +++ b/components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py @@ -53,7 +53,7 @@ class _ReducerHandlerWaitState(Enum): JOB_CONFIG = enum.auto() JOB_CONFIG_ACK = enum.auto() - SEARCH_WORKERS_DONE = enum.auto() + QUERY_WORKERS_DONE = enum.auto() REDUCER_DONE = enum.auto() @@ -179,8 +179,8 @@ async def handle_reducer_connection( await msg_queues.put_to_listeners(msg) recv_reducer_msg_task = asyncio.create_task(reader.readexactly(1)) - current_wait_state = _ReducerHandlerWaitState.SEARCH_WORKERS_DONE - elif _ReducerHandlerWaitState.SEARCH_WORKERS_DONE == current_wait_state: + current_wait_state = _ReducerHandlerWaitState.QUERY_WORKERS_DONE + elif _ReducerHandlerWaitState.QUERY_WORKERS_DONE == current_wait_state: if recv_reducer_msg_task in done: await _handle_unexpected_msg_from_reducer(current_wait_state, msg_queues) return @@ -195,7 +195,7 @@ async def handle_reducer_connection( ) return - # Tell the reducer the search workers are done + # Tell the reducer the query workers are done await _send_msg_to_reducer(msgpack.packb({"done": True}), writer) recv_listener_msg_task = asyncio.create_task(msg_queues.get_from_listeners()) diff --git a/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py b/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py index e590de46b..4f49a7c1a 100644 --- a/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py +++ b/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py @@ -1,11 +1,21 @@ import asyncio import datetime +from abc import ABC, abstractmethod from enum import auto, Enum from typing import Any, Dict, List, Optional -from job_orchestration.scheduler.constants import CompressionTaskStatus, SearchTaskStatus -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.search.reducer_handler import ReducerHandlerMessageQueues +from job_orchestration.scheduler.constants import ( + CompressionTaskStatus, + QueryJobType, + QueryTaskStatus, +) +from job_orchestration.scheduler.job_config import ( + ExtractIrJobConfig, + ExtractJsonJobConfig, + QueryJobConfig, + SearchJobConfig, +) +from job_orchestration.scheduler.query.reducer_handler import ReducerHandlerMessageQueues from pydantic import BaseModel, validator @@ -35,24 +45,59 @@ class InternalJobState(Enum): RUNNING = auto() -class SearchJob(BaseModel): +class QueryJob(BaseModel, ABC): id: str - search_config: SearchConfig state: InternalJobState start_time: Optional[datetime.datetime] + current_sub_job_async_task_result: Optional[Any] + + @abstractmethod + def get_type(self) -> QueryJobType: ... + + @abstractmethod + def get_config(self) -> QueryJobConfig: ... + + +class ExtractIrJob(QueryJob): + extract_ir_config: ExtractIrJobConfig + + def get_type(self) -> QueryJobType: + return QueryJobType.EXTRACT_IR + + def get_config(self) -> QueryJobConfig: + return self.extract_ir_config + + +class ExtractJsonJob(QueryJob): + extract_json_config: ExtractJsonJobConfig + + def get_type(self) -> QueryJobType: + return QueryJobType.EXTRACT_JSON + + def get_config(self) -> QueryJobConfig: + return self.extract_json_config + + +class SearchJob(QueryJob): + search_config: SearchJobConfig num_archives_to_search: int num_archives_searched: int remaining_archives_for_search: List[Dict[str, Any]] - current_sub_job_async_task_result: Optional[Any] reducer_acquisition_task: Optional[asyncio.Task] reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] + def get_type(self) -> QueryJobType: + return QueryJobType.SEARCH_OR_AGGREGATION + + def get_config(self) -> QueryJobConfig: + return self.search_config + class Config: # To allow asyncio.Task and asyncio.Queue arbitrary_types_allowed = True -class SearchTaskResult(BaseModel): - status: SearchTaskStatus +class QueryTaskResult(BaseModel): + status: QueryTaskStatus task_id: str duration: float error_log_path: Optional[str] diff --git a/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py b/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py deleted file mode 100644 index b287b3945..000000000 --- a/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py +++ /dev/null @@ -1,736 +0,0 @@ -""" -A scheduler for scheduling search jobs in the CLP package. - -NOTE: This scheduler currently only has partial handling for failures of the database. Specifically, -in the event that the database is unreachable, the scheduler will continue running as if reads from -the database return no records and writes to the database always fail. Failed writes are currently -silently ignored, in which case the state of the database won't match the scheduler's internal -state. If the database comes back up, the scheduler will eventually reconnect to it and reads/writes -will function as normal again. However, the mismatched state may lead to unexpected behaviour like -jobs seemingly being stuck in the "RUNNING" state or jobs being repeated, which in turn will create -duplicated search results in the results cache, possibly long after the results had already been -cleared from the cache. Unfortunately, these effects will require manual intervention to clean-up. -TODO Address this limitation. -""" - -from __future__ import annotations - -import argparse -import asyncio -import contextlib -import datetime -import logging -import os -import pathlib -import sys -from pathlib import Path -from typing import Dict, List, Optional - -import celery -import msgpack -import pymongo -from clp_py_utils.clp_config import ( - CLP_METADATA_TABLE_PREFIX, - CLPConfig, - SEARCH_JOBS_TABLE_NAME, - SEARCH_TASKS_TABLE_NAME, -) -from clp_py_utils.clp_logging import get_logger, get_logging_formatter, set_logging_level -from clp_py_utils.core import read_yaml_config_file -from clp_py_utils.decorators import exception_default_value -from clp_py_utils.sql_adapter import SQL_Adapter -from job_orchestration.executor.search.fs_search_task import search -from job_orchestration.scheduler.constants import SearchJobStatus, SearchTaskStatus -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.scheduler_data import InternalJobState, SearchJob, SearchTaskResult -from job_orchestration.scheduler.search.reducer_handler import ( - handle_reducer_connection, - ReducerHandlerMessage, - ReducerHandlerMessageQueues, - ReducerHandlerMessageType, -) -from pydantic import ValidationError - -# Setup logging -logger = get_logger("search-job-handler") - -# Dictionary of active jobs indexed by job id -active_jobs: Dict[str, SearchJob] = {} - -reducer_connection_queue: Optional[asyncio.Queue] = None - - -def cancel_job_except_reducer(job: SearchJob): - """ - Cancels the job apart from releasing the reducer since that requires an async call. - NOTE: By keeping this method synchronous, the caller can cancel most of the job atomically, - making it easier to avoid using locks in concurrent tasks. - :param job: - """ - - if InternalJobState.RUNNING == job.state: - job.current_sub_job_async_task_result.revoke(terminate=True) - try: - job.current_sub_job_async_task_result.get() - except Exception: - pass - elif InternalJobState.WAITING_FOR_REDUCER == job.state: - job.reducer_acquisition_task.cancel() - - -async def release_reducer_for_job(job: SearchJob): - """ - Releases the reducer assigned to the given job - :param job: - """ - - if job.reducer_handler_msg_queues is not None: - # Signal the reducer to cancel the job - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await job.reducer_handler_msg_queues.put_to_handler(msg) - - -@exception_default_value(default=[]) -def fetch_new_search_jobs(db_conn) -> list: - """ - Fetches search jobs with status=PENDING from the database. - :param db_conn: - :return: The pending search jobs on success. An empty list if an exception occurs while - interacting with the database. - """ - with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: - db_cursor.execute( - f""" - SELECT {SEARCH_JOBS_TABLE_NAME}.id as job_id, - {SEARCH_JOBS_TABLE_NAME}.search_config - FROM {SEARCH_JOBS_TABLE_NAME} - WHERE {SEARCH_JOBS_TABLE_NAME}.status={SearchJobStatus.PENDING} - """ - ) - return db_cursor.fetchall() - - -@exception_default_value(default=[]) -def fetch_cancelling_search_jobs(db_conn) -> list: - """ - Fetches search jobs with status=CANCELLING from the database. - :param db_conn: - :return: The cancelling search jobs on success. An empty list if an exception occurs while - interacting with the database. - """ - with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: - db_cursor.execute( - f""" - SELECT {SEARCH_JOBS_TABLE_NAME}.id as job_id - FROM {SEARCH_JOBS_TABLE_NAME} - WHERE {SEARCH_JOBS_TABLE_NAME}.status={SearchJobStatus.CANCELLING} - """ - ) - return db_cursor.fetchall() - - -@exception_default_value(default=False) -def set_job_or_task_status( - db_conn, - table_name: str, - job_id: str, - status: SearchJobStatus | SearchTaskStatus, - prev_status: Optional[SearchJobStatus | SearchTaskStatus] = None, - **kwargs, -) -> bool: - """ - Sets the status of the job or the tasks identified by `job_id` to `status`. If `prev_status` is - specified, the update is conditional on the job/task's current status matching `prev_status`. If - `kwargs` are specified, the fields identified by the args are also updated. - :param db_conn: - :param table_name: - :param job_id: - :param status: - :param prev_status: - :param kwargs: - :return: True on success, False if the update fails or an exception occurs while interacting - with the database. - """ - field_set_expressions = [f"status={status}"] - if SEARCH_JOBS_TABLE_NAME == table_name: - id_col_name = "id" - field_set_expressions.extend([f'{k}="{v}"' for k, v in kwargs.items()]) - elif SEARCH_TASKS_TABLE_NAME == table_name: - id_col_name = "job_id" - field_set_expressions.extend([f"{k}={v}" for k, v in kwargs.items()]) - else: - raise ValueError(f"Unsupported table name {table_name}") - update = ( - f'UPDATE {table_name} SET {", ".join(field_set_expressions)} WHERE {id_col_name}={job_id}' - ) - - if prev_status is not None: - update += f" AND status={prev_status}" - - with contextlib.closing(db_conn.cursor()) as cursor: - cursor.execute(update) - db_conn.commit() - rval = cursor.rowcount != 0 - return rval - - -async def handle_cancelling_search_jobs(db_conn_pool) -> None: - global active_jobs - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - cancelling_jobs = fetch_cancelling_search_jobs(db_conn) - - for cancelling_job in cancelling_jobs: - job_id = str(cancelling_job["job_id"]) - if job_id in active_jobs: - job = active_jobs.pop(job_id) - cancel_job_except_reducer(job) - # Perform any async tasks last so that it's easier to reason about synchronization - # issues between concurrent tasks - await release_reducer_for_job(job) - else: - continue - - set_job_or_task_status( - db_conn, - SEARCH_TASKS_TABLE_NAME, - job_id, - SearchTaskStatus.CANCELLED, - SearchTaskStatus.PENDING, - duration=0, - ) - - set_job_or_task_status( - db_conn, - SEARCH_TASKS_TABLE_NAME, - job_id, - SearchTaskStatus.CANCELLED, - SearchTaskStatus.RUNNING, - duration="TIMESTAMPDIFF(MICROSECOND, start_time, NOW())/1000000.0", - ) - - if set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - SearchJobStatus.CANCELLED, - SearchJobStatus.CANCELLING, - duration=(datetime.datetime.now() - job.start_time).total_seconds(), - ): - logger.info(f"Cancelled job {job_id}.") - else: - logger.error(f"Failed to cancel job {job_id}.") - - -def insert_search_tasks_into_db(db_conn, job_id, archive_ids: List[str]) -> List[int]: - task_ids = [] - with contextlib.closing(db_conn.cursor()) as cursor: - for archive_id in archive_ids: - cursor.execute( - f""" - INSERT INTO {SEARCH_TASKS_TABLE_NAME} - (job_id, archive_id) - VALUES({job_id}, '{archive_id}') - """ - ) - task_ids.append(cursor.lastrowid) - db_conn.commit() - return task_ids - - -@exception_default_value(default=[]) -def get_archives_for_search( - db_conn, - search_config: SearchConfig, -): - query = f"""SELECT id as archive_id, end_timestamp - FROM {CLP_METADATA_TABLE_PREFIX}archives - """ - filter_clauses = [] - if search_config.end_timestamp is not None: - filter_clauses.append(f"begin_timestamp <= {search_config.end_timestamp}") - if search_config.begin_timestamp is not None: - filter_clauses.append(f"end_timestamp >= {search_config.begin_timestamp}") - if search_config.tags is not None: - filter_clauses.append( - f"id IN (SELECT archive_id FROM {CLP_METADATA_TABLE_PREFIX}archive_tags WHERE " - f"tag_id IN (SELECT tag_id FROM {CLP_METADATA_TABLE_PREFIX}tags WHERE tag_name IN " - f"(%s)))" % ", ".join(["%s" for _ in search_config.tags]) - ) - if len(filter_clauses) > 0: - query += " WHERE " + " AND ".join(filter_clauses) - query += " ORDER BY end_timestamp DESC" - - with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: - if search_config.tags is not None: - cursor.execute(query, tuple(search_config.tags)) - else: - cursor.execute(query) - archives_for_search = list(cursor.fetchall()) - return archives_for_search - - -def get_task_group_for_job( - archive_ids: List[str], - task_ids: List[int], - job_id: str, - search_config: SearchConfig, - clp_metadata_db_conn_params: Dict[str, any], - results_cache_uri: str, -): - search_config_obj = search_config.dict() - return celery.group( - search.s( - job_id=job_id, - archive_id=archive_ids[i], - task_id=task_ids[i], - search_config_obj=search_config_obj, - clp_metadata_db_conn_params=clp_metadata_db_conn_params, - results_cache_uri=results_cache_uri, - ) - for i in range(len(archive_ids)) - ) - - -def dispatch_search_job( - db_conn, - job: SearchJob, - archives_for_search: List[Dict[str, any]], - clp_metadata_db_conn_params: Dict[str, any], - results_cache_uri: str, -) -> None: - global active_jobs - archive_ids = [archive["archive_id"] for archive in archives_for_search] - task_ids = insert_search_tasks_into_db(db_conn, job.id, archive_ids) - - task_group = get_task_group_for_job( - archive_ids, - task_ids, - job.id, - job.search_config, - clp_metadata_db_conn_params, - results_cache_uri, - ) - job.current_sub_job_async_task_result = task_group.apply_async() - job.state = InternalJobState.RUNNING - - -async def acquire_reducer_for_job(job: SearchJob): - reducer_host: Optional[str] = None - reducer_port: Optional[int] = None - reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] = None - while True: - reducer_host, reducer_port, reducer_handler_msg_queues = ( - await reducer_connection_queue.get() - ) - """ - Below, the task can either be cancelled before sending the job config to the reducer or - before the reducer acknowledges the job. If the task is cancelled before we send the job - config to the reducer, then we have two options: - - 1. Put the reducer's connection info back in the queue for another job to pick up. - 2. Tell the reducer to restart its job handling loop. - - If the task is cancelled after we've sent the job config to the reducer, then we have to - use option (2), so we use option (2) in both cases. - """ - try: - msg = ReducerHandlerMessage( - ReducerHandlerMessageType.AGGREGATION_CONFIG, job.search_config.aggregation_config - ) - await reducer_handler_msg_queues.put_to_handler(msg) - - msg = await reducer_handler_msg_queues.get_from_handler() - if msg.msg_type == ReducerHandlerMessageType.SUCCESS: - break - elif msg.msg_type != ReducerHandlerMessageType.FAILURE: - error_msg = f"Unexpected msg_type: {msg.msg_type.name}" - raise NotImplementedError(error_msg) - except asyncio.CancelledError: - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await reducer_handler_msg_queues.put_to_handler(msg) - raise - - job.reducer_handler_msg_queues = reducer_handler_msg_queues - job.search_config.aggregation_config.reducer_host = reducer_host - job.search_config.aggregation_config.reducer_port = reducer_port - job.state = InternalJobState.WAITING_FOR_DISPATCH - job.reducer_acquisition_task = None - - logger.info(f"Got reducer for job {job.id} at {reducer_host}:{reducer_port}") - - -def handle_pending_search_jobs( - db_conn_pool, - clp_metadata_db_conn_params: Dict[str, any], - results_cache_uri: str, - num_archives_to_search_per_sub_job: int, -) -> List[asyncio.Task]: - global active_jobs - - reducer_acquisition_tasks = [] - - pending_jobs = [ - job for job in active_jobs.values() if InternalJobState.WAITING_FOR_DISPATCH == job.state - ] - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - for job in fetch_new_search_jobs(db_conn): - job_id = str(job["job_id"]) - - # Avoid double-dispatch when a job is WAITING_FOR_REDUCER - if job_id in active_jobs: - continue - - search_config = SearchConfig.parse_obj(msgpack.unpackb(job["search_config"])) - archives_for_search = get_archives_for_search(db_conn, search_config) - if len(archives_for_search) == 0: - if set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - SearchJobStatus.SUCCEEDED, - SearchJobStatus.PENDING, - start_time=datetime.datetime.now(), - num_tasks=0, - duration=0, - ): - logger.info(f"No matching archives, skipping job {job['job_id']}.") - continue - - new_search_job = SearchJob( - id=job_id, - search_config=search_config, - state=InternalJobState.WAITING_FOR_DISPATCH, - num_archives_to_search=len(archives_for_search), - num_archives_searched=0, - remaining_archives_for_search=archives_for_search, - ) - - if search_config.aggregation_config is not None: - new_search_job.search_config.aggregation_config.job_id = job["job_id"] - new_search_job.state = InternalJobState.WAITING_FOR_REDUCER - new_search_job.reducer_acquisition_task = asyncio.create_task( - acquire_reducer_for_job(new_search_job) - ) - reducer_acquisition_tasks.append(new_search_job.reducer_acquisition_task) - else: - pending_jobs.append(new_search_job) - active_jobs[job_id] = new_search_job - - for job in pending_jobs: - job_id = job.id - - if ( - job.search_config.network_address is None - and len(job.remaining_archives_for_search) > num_archives_to_search_per_sub_job - ): - archives_for_search = job.remaining_archives_for_search[ - :num_archives_to_search_per_sub_job - ] - job.remaining_archives_for_search = job.remaining_archives_for_search[ - num_archives_to_search_per_sub_job: - ] - else: - archives_for_search = job.remaining_archives_for_search - job.remaining_archives_for_search = [] - - dispatch_search_job( - db_conn, job, archives_for_search, clp_metadata_db_conn_params, results_cache_uri - ) - logger.info( - f"Dispatched job {job_id} with {len(archives_for_search)} archives to search." - ) - start_time = datetime.datetime.now() - job.start_time = start_time - set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - SearchJobStatus.RUNNING, - SearchJobStatus.PENDING, - start_time=start_time, - num_tasks=job.num_archives_to_search, - ) - - return reducer_acquisition_tasks - - -def try_getting_task_result(async_task_result): - if not async_task_result.ready(): - return None - return async_task_result.get() - - -def found_max_num_latest_results( - results_cache_uri: str, - job_id: str, - max_num_results: int, - max_timestamp_in_remaining_archives: int, -) -> bool: - with pymongo.MongoClient(results_cache_uri) as results_cache_client: - results_cache_collection = results_cache_client.get_default_database()[job_id] - results_count = results_cache_collection.count_documents({}) - if results_count < max_num_results: - return False - - results = list( - results_cache_collection.find( - projection=["timestamp"], - sort=[("timestamp", pymongo.DESCENDING)], - limit=max_num_results, - ) - .sort("timestamp", pymongo.ASCENDING) - .limit(1) - ) - min_timestamp_in_top_results = 0 if len(results) == 0 else results[0]["timestamp"] - return max_timestamp_in_remaining_archives <= min_timestamp_in_top_results - - -async def check_job_status_and_update_db(db_conn_pool, results_cache_uri): - global active_jobs - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - for job_id in [ - id for id, job in active_jobs.items() if InternalJobState.RUNNING == job.state - ]: - job = active_jobs[job_id] - is_reducer_job = job.reducer_handler_msg_queues is not None - - try: - returned_results = try_getting_task_result(job.current_sub_job_async_task_result) - except Exception as e: - logger.error(f"Job `{job_id}` failed: {e}.") - # Clean up - if is_reducer_job: - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await job.reducer_handler_msg_queues.put_to_handler(msg) - - del active_jobs[job_id] - set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - SearchJobStatus.FAILED, - SearchJobStatus.RUNNING, - duration=(datetime.datetime.now() - job.start_time).total_seconds(), - ) - continue - - if returned_results is None: - continue - - new_job_status = SearchJobStatus.RUNNING - for task_result_obj in returned_results: - task_result = SearchTaskResult.parse_obj(task_result_obj) - task_id = task_result.task_id - task_status = task_result.status - if not task_status == SearchTaskStatus.SUCCEEDED: - new_job_status = SearchJobStatus.FAILED - logger.error( - f"Search task job-{job_id}-task-{task_id} failed. " - f"Check {task_result.error_log_path} for details." - ) - else: - job.num_archives_searched += 1 - logger.info( - f"Search task job-{job_id}-task-{task_id} succeeded in " - f"{task_result.duration} second(s)." - ) - - if new_job_status != SearchJobStatus.FAILED: - max_num_results = job.search_config.max_num_results - # Check if we've searched all archives - if len(job.remaining_archives_for_search) == 0: - new_job_status = SearchJobStatus.SUCCEEDED - # Check if we've reached max results - elif False == is_reducer_job and max_num_results > 0: - if found_max_num_latest_results( - results_cache_uri, - job_id, - max_num_results, - job.remaining_archives_for_search[0]["end_timestamp"], - ): - new_job_status = SearchJobStatus.SUCCEEDED - if new_job_status == SearchJobStatus.RUNNING: - job.current_sub_job_async_task_result = None - job.state = InternalJobState.WAITING_FOR_DISPATCH - logger.info(f"Job {job_id} waiting for more archives to search.") - set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - SearchJobStatus.RUNNING, - SearchJobStatus.RUNNING, - num_tasks_completed=job.num_archives_searched, - ) - continue - - reducer_failed = False - if is_reducer_job: - # Notify reducer that it should have received all results - msg = ReducerHandlerMessage(ReducerHandlerMessageType.SUCCESS) - await job.reducer_handler_msg_queues.put_to_handler(msg) - - msg = await job.reducer_handler_msg_queues.get_from_handler() - if ReducerHandlerMessageType.FAILURE == msg.msg_type: - reducer_failed = True - new_job_status = SearchJobStatus.FAILED - elif ReducerHandlerMessageType.SUCCESS != msg.msg_type: - error_msg = f"Unexpected msg_type: {msg.msg_type.name}" - raise NotImplementedError(error_msg) - - # We set the status regardless of the job's previous status to handle the case where the - # job is cancelled (status = CANCELLING) while we're in this method. - if set_job_or_task_status( - db_conn, - SEARCH_JOBS_TABLE_NAME, - job_id, - new_job_status, - num_tasks_completed=job.num_archives_searched, - duration=(datetime.datetime.now() - job.start_time).total_seconds(), - ): - if new_job_status == SearchJobStatus.SUCCEEDED: - logger.info(f"Completed job {job_id}.") - elif reducer_failed: - logger.error(f"Completed job {job_id} with failing reducer.") - else: - logger.info(f"Completed job {job_id} with failing tasks.") - del active_jobs[job_id] - - -async def handle_job_updates(db_conn_pool, results_cache_uri: str, jobs_poll_delay: float): - while True: - await handle_cancelling_search_jobs(db_conn_pool) - await check_job_status_and_update_db(db_conn_pool, results_cache_uri) - await asyncio.sleep(jobs_poll_delay) - - -async def handle_jobs( - db_conn_pool, - clp_metadata_db_conn_params: Dict[str, any], - results_cache_uri: str, - jobs_poll_delay: float, - num_archives_to_search_per_sub_job: int, -) -> None: - handle_updating_task = asyncio.create_task( - handle_job_updates(db_conn_pool, results_cache_uri, jobs_poll_delay) - ) - - tasks = [handle_updating_task] - while True: - reducer_acquisition_tasks = handle_pending_search_jobs( - db_conn_pool, - clp_metadata_db_conn_params, - results_cache_uri, - num_archives_to_search_per_sub_job, - ) - if 0 == len(reducer_acquisition_tasks): - tasks.append(asyncio.create_task(asyncio.sleep(jobs_poll_delay))) - else: - tasks.extend(reducer_acquisition_tasks) - - done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - if handle_updating_task in done: - logger.error("handle_job_updates completed unexpectedly.") - try: - handle_updating_task.result() - except Exception: - logger.exception("handle_job_updates failed.") - return - tasks = list(pending) - - -async def main(argv: List[str]) -> int: - global reducer_connection_queue - - args_parser = argparse.ArgumentParser(description="Wait for and run search jobs.") - args_parser.add_argument("--config", "-c", required=True, help="CLP configuration file.") - - parsed_args = args_parser.parse_args(argv[1:]) - - # Setup logging to file - log_file = Path(os.getenv("CLP_LOGS_DIR")) / "search_scheduler.log" - logging_file_handler = logging.FileHandler(filename=log_file, encoding="utf-8") - logging_file_handler.setFormatter(get_logging_formatter()) - logger.addHandler(logging_file_handler) - - # Update logging level based on config - set_logging_level(logger, os.getenv("CLP_LOGGING_LEVEL")) - - # Load configuration - config_path = pathlib.Path(parsed_args.config) - try: - clp_config = CLPConfig.parse_obj(read_yaml_config_file(config_path)) - except ValidationError as err: - logger.error(err) - return -1 - except Exception as ex: - logger.error(ex) - return -1 - - reducer_connection_queue = asyncio.Queue(32) - - sql_adapter = SQL_Adapter(clp_config.database) - - logger.debug(f"Job polling interval {clp_config.search_scheduler.jobs_poll_delay} seconds.") - try: - reducer_handler = await asyncio.start_server( - lambda reader, writer: handle_reducer_connection( - reader, writer, reducer_connection_queue - ), - clp_config.search_scheduler.host, - clp_config.search_scheduler.port, - ) - db_conn_pool = sql_adapter.create_connection_pool( - logger=logger, pool_size=2, disable_localhost_socket_connection=True - ) - - if False == db_conn_pool.alive(): - logger.error( - f"Failed to connect to archive database " - f"{clp_config.database.host}:{clp_config.database.port}." - ) - return -1 - - logger.info( - f"Connected to archive database" - f" {clp_config.database.host}:{clp_config.database.port}." - ) - logger.info("Search scheduler started.") - batch_size = clp_config.search_scheduler.num_archives_to_search_per_sub_job - job_handler = asyncio.create_task( - handle_jobs( - db_conn_pool=db_conn_pool, - clp_metadata_db_conn_params=clp_config.database.get_clp_connection_params_and_type( - True - ), - results_cache_uri=clp_config.results_cache.get_uri(), - jobs_poll_delay=clp_config.search_scheduler.jobs_poll_delay, - num_archives_to_search_per_sub_job=batch_size, - ) - ) - reducer_handler = asyncio.create_task(reducer_handler.serve_forever()) - done, pending = await asyncio.wait( - [job_handler, reducer_handler], return_when=asyncio.FIRST_COMPLETED - ) - if reducer_handler in done: - logger.error("reducer_handler completed unexpectedly.") - try: - reducer_handler.result() - except Exception: - logger.exception("reducer_handler failed.") - if job_handler in done: - logger.error("job_handler completed unexpectedly.") - try: - job_handler.result() - except Exception: - logger.exception("job_handler failed.") - except Exception: - logger.exception(f"Uncaught exception in job handling loop.") - - return 0 - - -if "__main__" == __name__: - sys.exit(asyncio.run(main(sys.argv))) diff --git a/components/log-viewer-webui/.gitignore b/components/log-viewer-webui/.gitignore new file mode 100644 index 000000000..b6448e2f6 --- /dev/null +++ b/components/log-viewer-webui/.gitignore @@ -0,0 +1,2 @@ +# Dependencies +node_modules diff --git a/components/log-viewer-webui/README.md b/components/log-viewer-webui/README.md new file mode 100644 index 000000000..e052ec9d9 --- /dev/null +++ b/components/log-viewer-webui/README.md @@ -0,0 +1,9 @@ +# Log Viewer WebUI + +A webapp that allows us to serve the [log-viewer] and integrate it with CLP's [webui]. + +See the [docs] for more details. + +[docs]: https://docs.yscope.com/clp/main/dev-guide/components-log-viewer-webui +[log-viewer]: https://github.com/y-scope/yscope-log-viewer +[webui]: ../webui diff --git a/components/log-viewer-webui/client/.gitignore b/components/log-viewer-webui/client/.gitignore new file mode 100644 index 000000000..0fc763fdd --- /dev/null +++ b/components/log-viewer-webui/client/.gitignore @@ -0,0 +1,9 @@ +# Dependencies +/node_modules + +# Build +/dist + +# IDEs +/.idea +/.vscode diff --git a/components/log-viewer-webui/client/package-lock.json b/components/log-viewer-webui/client/package-lock.json new file mode 100644 index 000000000..26c9953a2 --- /dev/null +++ b/components/log-viewer-webui/client/package-lock.json @@ -0,0 +1,11088 @@ +{ + "name": "log-viewer-webui-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui-client", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/joy": "^5.0.0-beta.48", + "@types/react": "^18.3.3", + "axios": "^1.7.2", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "babel-loader": "^9.1.3", + "css-loader": "^7.1.2", + "eslint-config-yscope": "latest", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.0", + "react-refresh": "^0.14.2", + "style-loader": "^4.0.0", + "webpack": "^5.95.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz", + "integrity": "sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz", + "integrity": "sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/traverse": "^7.25.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz", + "integrity": "sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "regexpu-core": "^6.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz", + "integrity": "sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz", + "integrity": "sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz", + "integrity": "sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-wrap-function": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz", + "integrity": "sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz", + "integrity": "sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz", + "integrity": "sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz", + "integrity": "sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz", + "integrity": "sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz", + "integrity": "sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz", + "integrity": "sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/plugin-transform-optional-chaining": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz", + "integrity": "sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", + "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", + "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz", + "integrity": "sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.8.tgz", + "integrity": "sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz", + "integrity": "sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz", + "integrity": "sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz", + "integrity": "sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz", + "integrity": "sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz", + "integrity": "sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz", + "integrity": "sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/traverse": "^7.25.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz", + "integrity": "sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/template": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz", + "integrity": "sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz", + "integrity": "sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz", + "integrity": "sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz", + "integrity": "sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz", + "integrity": "sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz", + "integrity": "sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz", + "integrity": "sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz", + "integrity": "sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz", + "integrity": "sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz", + "integrity": "sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz", + "integrity": "sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz", + "integrity": "sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz", + "integrity": "sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz", + "integrity": "sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz", + "integrity": "sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz", + "integrity": "sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz", + "integrity": "sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz", + "integrity": "sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz", + "integrity": "sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-transform-parameters": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz", + "integrity": "sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz", + "integrity": "sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz", + "integrity": "sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz", + "integrity": "sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", + "integrity": "sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz", + "integrity": "sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz", + "integrity": "sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.7.tgz", + "integrity": "sha512-r0QY7NVU8OnrwE+w2IWiRom0wwsTbjx4+xH2RTd7AVdof3uurXOF+/mXHQDRk+2jIvWgSaCHKMgggfvM4dyUGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.7.tgz", + "integrity": "sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-jsx": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.7.tgz", + "integrity": "sha512-5yd3lH1PWxzW6IZj+p+Y4OLQzz0/LzlOG8vGqonHfVR3euf1vyzyMUJk9Ac+m97BH46mFc/98t9PmYLyvgL3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.7.tgz", + "integrity": "sha512-6YTHJ7yjjgYqGc8S+CbEXhLICODk0Tn92j+vNJo07HFk9t3bjFgAKxPLFhHwF2NjmQVSI1zBRfBWUeVBa2osfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz", + "integrity": "sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz", + "integrity": "sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz", + "integrity": "sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz", + "integrity": "sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz", + "integrity": "sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz", + "integrity": "sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz", + "integrity": "sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz", + "integrity": "sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz", + "integrity": "sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz", + "integrity": "sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz", + "integrity": "sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.8.tgz", + "integrity": "sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.8", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.25.7", + "@babel/plugin-syntax-import-attributes": "^7.25.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.8", + "@babel/plugin-transform-async-to-generator": "^7.25.7", + "@babel/plugin-transform-block-scoped-functions": "^7.25.7", + "@babel/plugin-transform-block-scoping": "^7.25.7", + "@babel/plugin-transform-class-properties": "^7.25.7", + "@babel/plugin-transform-class-static-block": "^7.25.8", + "@babel/plugin-transform-classes": "^7.25.7", + "@babel/plugin-transform-computed-properties": "^7.25.7", + "@babel/plugin-transform-destructuring": "^7.25.7", + "@babel/plugin-transform-dotall-regex": "^7.25.7", + "@babel/plugin-transform-duplicate-keys": "^7.25.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-dynamic-import": "^7.25.8", + "@babel/plugin-transform-exponentiation-operator": "^7.25.7", + "@babel/plugin-transform-export-namespace-from": "^7.25.8", + "@babel/plugin-transform-for-of": "^7.25.7", + "@babel/plugin-transform-function-name": "^7.25.7", + "@babel/plugin-transform-json-strings": "^7.25.8", + "@babel/plugin-transform-literals": "^7.25.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.8", + "@babel/plugin-transform-member-expression-literals": "^7.25.7", + "@babel/plugin-transform-modules-amd": "^7.25.7", + "@babel/plugin-transform-modules-commonjs": "^7.25.7", + "@babel/plugin-transform-modules-systemjs": "^7.25.7", + "@babel/plugin-transform-modules-umd": "^7.25.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-new-target": "^7.25.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.8", + "@babel/plugin-transform-numeric-separator": "^7.25.8", + "@babel/plugin-transform-object-rest-spread": "^7.25.8", + "@babel/plugin-transform-object-super": "^7.25.7", + "@babel/plugin-transform-optional-catch-binding": "^7.25.8", + "@babel/plugin-transform-optional-chaining": "^7.25.8", + "@babel/plugin-transform-parameters": "^7.25.7", + "@babel/plugin-transform-private-methods": "^7.25.7", + "@babel/plugin-transform-private-property-in-object": "^7.25.8", + "@babel/plugin-transform-property-literals": "^7.25.7", + "@babel/plugin-transform-regenerator": "^7.25.7", + "@babel/plugin-transform-reserved-words": "^7.25.7", + "@babel/plugin-transform-shorthand-properties": "^7.25.7", + "@babel/plugin-transform-spread": "^7.25.7", + "@babel/plugin-transform-sticky-regex": "^7.25.7", + "@babel/plugin-transform-template-literals": "^7.25.7", + "@babel/plugin-transform-typeof-symbol": "^7.25.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.7", + "@babel/plugin-transform-unicode-property-regex": "^7.25.7", + "@babel/plugin-transform-unicode-regex": "^7.25.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.7.tgz", + "integrity": "sha512-GjV0/mUEEXpi1U5ZgDprMRRgajGMRW3G5FjMr5KLKD8nT2fTG8+h/klV3+6Dm5739QE+K5+2e91qFKAYI3pmRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "@babel/plugin-transform-react-display-name": "^7.25.7", + "@babel/plugin-transform-react-jsx": "^7.25.7", + "@babel/plugin-transform-react-jsx-development": "^7.25.7", + "@babel/plugin-transform-react-pure-annotations": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/joy": { + "version": "5.0.0-beta.48", + "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.48.tgz", + "integrity": "sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.16.1", + "@mui/system": "^5.16.1", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.1", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.18", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.1.tgz", + "integrity": "sha512-c5c2C8Mos5tTQd+NWpqwEu7VT6SSRooAguFPMj1cp2RkTYl1ynKoXo8MWy3k4rkbzoeYHrqC2UlUzsroAN7wtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.8.1.tgz", + "integrity": "sha512-k1Eb6rcjMP+mmjvj+vd9y5KUdWn1OBkkPLHXhsrHt5lCDFZxJEs0aVQzE5lpYrtVZVkpc5esTtss/cPJux0lfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^1.8.1", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.8.1.tgz", + "integrity": "sha512-4+40H3lHYTN8OWz+US8CamVkO+2hxNLp9+CAjorI7top/lHqemhpJvKA1LD9Uh+WMY9DYWiWpL2+SZ2wAXY9fQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.7.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz", + "integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.38.1.tgz", + "integrity": "sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "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", + "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", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "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", + "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", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", + "integrity": "sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-yscope": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/eslint-config-yscope/-/eslint-config-yscope-0.0.31.tgz", + "integrity": "sha512-cA6sS3G4Ydoht/CvST7C7moqJO+NiKl0InQtkX5YKocXAQE6KZ/VW9/kORdNnpIGCLwkqvMOa7y4XNJZnTfubw==", + "dev": true, + "peerDependencies": { + "@stylistic/eslint-plugin-js": "^1.6.2", + "@stylistic/eslint-plugin-jsx": "^1.6.2", + "@stylistic/eslint-plugin-plus": "^1.6.2", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import-newlines": "^1.4.0", + "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-no-autofix": "^1.2.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-simple-import-sort": "^12.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "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-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.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": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import-newlines": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.4.0.tgz", + "integrity": "sha512-+Cz1x2xBLtI9gJbmuYEpvY7F8K75wskBmJ7rk4VRObIJo+jklUJaejFJgtnWeL0dCFWabGEkhausrikXaNbtoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "import-linter": "lib/index.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.11.0.tgz", + "integrity": "sha512-d12JHJDPNo7IFwTOAItCeJY1hcqoIxE0lHA8infQByLilQ9xkqrRa6laWCnsuCrf+8rUnvxXY1XuTbibRBNylA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-no-autofix": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-autofix/-/eslint-plugin-no-autofix-1.2.3.tgz", + "integrity": "sha512-JFSYe82Da2A8Krh+Gfq7+3X2pchTScKgmrlMKIA4HmV6t5xGBF/kgjiFL3YTWRQXQ0NB9eOqpcxh6SuLtQUFjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0", + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "eslint": ">= 5.12.1" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", + "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.2.tgz", + "integrity": "sha512-q7xp/FO9RGBVoTKNItkdX1jKLscLFkgn/dLVFNYbHVbfHLBk6DYW5nsQ8kCzIWcgKP/kUBocetjvav6lD8YfCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz", + "integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz", + "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", + "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "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", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0", + "peer": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz", + "integrity": "sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.19.2", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/components/log-viewer-webui/client/package.json b/components/log-viewer-webui/client/package.json new file mode 100644 index 000000000..906d6eba7 --- /dev/null +++ b/components/log-viewer-webui/client/package.json @@ -0,0 +1,45 @@ +{ + "name": "log-viewer-webui-client", + "version": "0.1.0", + "description": "", + "main": "src/index.jsx", + "scripts": { + "build": "webpack --define-process-env-node-env production", + "lint:check": "npx eslint --no-eslintrc --config package.json src webpack.config.js", + "lint:fix": "npx eslint --fix --no-eslintrc --config package.json src webpack.config.js", + "start": "webpack serve" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "type": "module", + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "babel-loader": "^9.1.3", + "css-loader": "^7.1.2", + "eslint-config-yscope": "latest", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.0", + "react-refresh": "^0.14.2", + "style-loader": "^4.0.0", + "webpack": "^5.95.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0" + }, + "eslintConfig": { + "extends": [ + "yscope/react" + ] + }, + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/joy": "^5.0.0-beta.48", + "@types/react": "^18.3.3", + "axios": "^1.7.2", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } +} diff --git a/components/log-viewer-webui/client/public/index.html b/components/log-viewer-webui/client/public/index.html new file mode 100644 index 000000000..c2e5d6b78 --- /dev/null +++ b/components/log-viewer-webui/client/public/index.html @@ -0,0 +1,17 @@ + + + + Log Viewer + + + + + + + + +
+ + diff --git a/components/log-viewer-webui/client/src/App.jsx b/components/log-viewer-webui/client/src/App.jsx new file mode 100644 index 000000000..ef032f85c --- /dev/null +++ b/components/log-viewer-webui/client/src/App.jsx @@ -0,0 +1,20 @@ +import {CssVarsProvider} from "@mui/joy/styles/CssVarsProvider"; + +import LOCAL_STORAGE_KEY from "./typings/LOCAL_STORAGE_KEY.js"; +import QueryStatus from "./ui/QueryStatus.jsx"; + + +/** + * Renders the main application. + * + * @return {JSX.Element} + */ +const App = () => { + return ( + + + + ); +}; + +export default App; diff --git a/components/log-viewer-webui/client/src/api/query.js b/components/log-viewer-webui/client/src/api/query.js new file mode 100644 index 000000000..eda1db21c --- /dev/null +++ b/components/log-viewer-webui/client/src/api/query.js @@ -0,0 +1,43 @@ +import axios from "axios"; + + +/** + * @typedef {object} ExtractIrResp + * @property {number} begin_msg_ix + * @property {number} end_msg_ix + * @property {string} file_split_id + * @property {boolean} is_last_ir_chunk + * @property {string} orig_file_id + * @property {string} path + * @property {string} _id + */ + +/** + * @typedef {object} ExtractJsonResp + * @property {number} begin_msg_ix + * @property {number} end_msg_ix + * @property {boolean} is_last_ir_chunk + * @property {string} orig_file_id + * @property {string} path + * @property {string} _id + */ + +/** + * Submits a job to extract the stream that contains a given log event. The stream is extracted + * either as a CLP IR or a JSON Lines file. + * + * @param {QUERY_JOB_TYPE} extractJobType + * @param {string} streamId + * @param {number} logEventIdx + * @param {Function} onUploadProgress Callback to handle upload progress events. + * @return {Promise>} + */ +const submitExtractStreamJob = async (extractJobType, streamId, logEventIdx, onUploadProgress) => { + return await axios.post( + "/query/extract-stream", + {extractJobType, streamId, logEventIdx}, + {onUploadProgress} + ); +}; + +export {submitExtractStreamJob}; diff --git a/components/log-viewer-webui/client/src/index.css b/components/log-viewer-webui/client/src/index.css new file mode 100644 index 000000000..2e657d8e6 --- /dev/null +++ b/components/log-viewer-webui/client/src/index.css @@ -0,0 +1,15 @@ +html { + height: 100%; + width: 100%; +} + +body { + margin: 0; + height: 100%; + width: 100%; +} + +#root { + height: 100%; + width: 100%; +} diff --git a/components/log-viewer-webui/client/src/index.jsx b/components/log-viewer-webui/client/src/index.jsx new file mode 100644 index 000000000..5cb6dbd4e --- /dev/null +++ b/components/log-viewer-webui/client/src/index.jsx @@ -0,0 +1,14 @@ +import {StrictMode} from "react"; +import {createRoot} from "react-dom/client"; + +import App from "./App"; + +import "./index.css"; + + +const root = createRoot(document.getElementById("root")); +root.render( + + + +); diff --git a/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js b/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js new file mode 100644 index 000000000..3218b9f48 --- /dev/null +++ b/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js @@ -0,0 +1,8 @@ +/** + * Enum of `window.localStorage` keys. + */ +const LOCAL_STORAGE_KEY = Object.freeze({ + THEME: "theme", +}); + +export default LOCAL_STORAGE_KEY; diff --git a/components/log-viewer-webui/client/src/typings/query.js b/components/log-viewer-webui/client/src/typings/query.js new file mode 100644 index 000000000..b91a814f1 --- /dev/null +++ b/components/log-viewer-webui/client/src/typings/query.js @@ -0,0 +1,37 @@ +/** + * @typedef {number} QueryLoadingState + */ +let enumQueryLoadingState; +/** + * Enum of query loading state. + * + * @enum {QueryLoadingState} + */ +const QUERY_LOADING_STATES = Object.freeze({ + SUBMITTING: (enumQueryLoadingState = 0), + WAITING: ++enumQueryLoadingState, + LOADING: ++enumQueryLoadingState, +}); + +/** + * Descriptions for query loading states. + */ +const QUERY_LOADING_STATE_DESCRIPTIONS = Object.freeze({ + [QUERY_LOADING_STATES.SUBMITTING]: { + label: "Submitting query Job", + description: "Parsing arguments and submitting job to the server.", + }, + [QUERY_LOADING_STATES.WAITING]: { + label: "Waiting for job to finish", + description: "The job is running. Waiting for the job to finish.", + }, + [QUERY_LOADING_STATES.LOADING]: { + label: "Loading Log Viewer", + description: "The query has been completed and the results are being loaded.", + }, +}); + +export { + QUERY_LOADING_STATE_DESCRIPTIONS, + QUERY_LOADING_STATES, +}; diff --git a/components/log-viewer-webui/client/src/ui/Loading.css b/components/log-viewer-webui/client/src/ui/Loading.css new file mode 100644 index 000000000..d8bec1842 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/Loading.css @@ -0,0 +1,24 @@ +.loading-sheet { + height: 100%; + + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; +} + +.loading-progress-container { + width: 100%; +} + +.loading-stepper-container { + display: flex; + flex-grow: 1; + + align-items: center; +} + +.loading-stepper { + --Stepper-verticalGap: 2rem !important; +} diff --git a/components/log-viewer-webui/client/src/ui/Loading.jsx b/components/log-viewer-webui/client/src/ui/Loading.jsx new file mode 100644 index 000000000..e157d1224 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/Loading.jsx @@ -0,0 +1,130 @@ +import { + Box, + LinearProgress, + Sheet, + Step, + StepIndicator, + Stepper, + Typography, +} from "@mui/joy"; + +import { + QUERY_LOADING_STATE_DESCRIPTIONS, + QUERY_LOADING_STATES, +} from "../typings/query.js"; + +import "./Loading.css"; + + +/** + * Renders a step with a label and description. + * + * @param {object} props + * @param {string} props.description + * @param {boolean} props.isActive + * @param {boolean} props.isError + * @param {string} props.label + * @param {number | string} props.stepIndicatorText + * @return {React.ReactElement} + */ +const LoadingStep = ({ + description, + isActive, + isError, + label, + stepIndicatorText, +}) => { + let color = isActive ? + "primary" : + "neutral"; + + if (isError) { + color = "danger"; + } + + return ( + + {stepIndicatorText} + + } + > + + {label} + + + {description} + + + ); +}; + +/** + * Displays status of a pending query job. + * + * @param {object} props + * @param {QueryLoadState} props.currentState + * @param {string} props.errorMsg + * @return {React.ReactElement} + */ +const Loading = ({currentState, errorMsg}) => { + const steps = []; + Object.values(QUERY_LOADING_STATES).forEach((state) => { + const isActive = (currentState === state); + const stateDescription = QUERY_LOADING_STATE_DESCRIPTIONS[state]; + steps.push( + + ); + if (isActive && null !== errorMsg) { + steps.push( + + ); + } + }); + + return ( + <> + + + + + + + {steps} + + + + + ); +}; + +export default Loading; diff --git a/components/log-viewer-webui/client/src/ui/QueryStatus.jsx b/components/log-viewer-webui/client/src/ui/QueryStatus.jsx new file mode 100644 index 000000000..56a995980 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/QueryStatus.jsx @@ -0,0 +1,117 @@ +import { + useEffect, + useRef, + useState, +} from "react"; + +import {AxiosError} from "axios"; + +import {submitExtractStreamJob} from "../api/query.js"; +import {QUERY_LOADING_STATES} from "../typings/query.js"; +import Loading from "./Loading.jsx"; + + +let enumQueryType; +/* eslint-disable sort-keys */ +/** + * Note: This enum is duplicated from server, as it is non-trivial to include server enums from the + * client. + * + * Enum of job types, matching the `QueryJobType` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_TYPE = Object.freeze({ + SEARCH_OR_AGGREGATION: (enumQueryType = 0), + EXTRACT_IR: ++enumQueryType, + EXTRACT_JSON: ++enumQueryType, +}); +/* eslint-enable sort-keys */ + +/** + * Mapping between job type enums and stream type + */ +const EXTRACT_JOB_TYPE = Object.freeze({ + ir: QUERY_JOB_TYPE.EXTRACT_IR, + json: QUERY_JOB_TYPE.EXTRACT_JSON, +}); + +/** + * Submits queries and renders the query states. + * + * @return {React.ReactElement} + */ +const QueryStatus = () => { + const [queryState, setQueryState] = useState(QUERY_LOADING_STATES.SUBMITTING); + const [errorMsg, setErrorMsg] = useState(null); + const isFirstRun = useRef(true); + + useEffect(() => { + if (false === isFirstRun.current) { + return; + } + isFirstRun.current = false; + + const searchParams = new URLSearchParams(window.location.search); + const streamType = searchParams.get("type"); + const streamId = searchParams.get("streamId"); + const logEventIdx = searchParams.get("logEventIdx"); + + if (null === streamType || null === streamId || null === logEventIdx) { + const error = "Queries parameters are missing from the URL parameters."; + console.error(error); + setErrorMsg(error); + + return; + } + + const extractJobType = EXTRACT_JOB_TYPE[streamType]; + if ("undefined" === typeof extractJobType) { + const error = `Unsupported Stream type: ${streamType}`; + console.error(error); + setErrorMsg(error); + + return; + } + + submitExtractStreamJob( + extractJobType, + streamId, + Number(logEventIdx), + () => { + setQueryState(QUERY_LOADING_STATES.WAITING); + } + ) + .then(({data}) => { + setQueryState(QUERY_LOADING_STATES.LOADING); + + const innerLogEventNum = logEventIdx - data.begin_msg_ix + 1; + window.location = `/log-viewer/index.html?filePath=/streams/${data.path}` + + `#logEventNum=${innerLogEventNum}`; + }) + .catch((e) => { + let msg = "Unknown error."; + if (e instanceof AxiosError) { + msg = e.message; + if ("undefined" !== typeof e.response) { + if ("undefined" !== typeof e.response.data.message) { + msg = e.response.data.message; + } else { + msg = e.response.statusText; + } + } + } + console.error(msg, e); + setErrorMsg(msg); + }); + }, []); + + return ( + + ); +}; + +export default QueryStatus; diff --git a/components/log-viewer-webui/client/webpack.config.js b/components/log-viewer-webui/client/webpack.config.js new file mode 100644 index 000000000..61ec64bb6 --- /dev/null +++ b/components/log-viewer-webui/client/webpack.config.js @@ -0,0 +1,95 @@ +import HtmlWebpackPlugin from "html-webpack-plugin"; +import MiniCssExtractPlugin from "mini-css-extract-plugin"; +import * as path from "node:path"; +import {fileURLToPath} from "node:url"; + +import ReactRefreshPlugin from "@pmmmwh/react-refresh-webpack-plugin"; + + +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +const isProduction = "production" === process.env.NODE_ENV; + +const stylesHandler = isProduction ? + MiniCssExtractPlugin.loader : + "style-loader"; + +const plugins = [ + new HtmlWebpackPlugin({ + template: path.resolve(dirname, "public", "index.html"), + }), +]; + +const config = { + devServer: { + proxy: [ + { + context: ["/"], + target: "http://localhost:3000", + }, + ], + }, + devtool: isProduction ? + "source-map" : + "eval-source-map", + entry: path.resolve(dirname, "src", "index.jsx"), + module: { + rules: [ + { + test: /\.(js|jsx)$/i, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + presets: [ + "@babel/preset-env", + [ + "@babel/preset-react", + { + runtime: "automatic", + }, + ], + ], + plugins: isProduction ? + [] : + ["react-refresh/babel"], + }, + }, + }, + { + test: /\.css$/i, + use: [ + stylesHandler, + "css-loader", + ], + }, + ], + }, + output: { + path: path.resolve(dirname, "dist"), + filename: isProduction ? + "[name].[contenthash].bundle.js" : + "[name].bundle.js", + clean: true, + publicPath: "auto", + }, + plugins: plugins.concat(isProduction ? + [new MiniCssExtractPlugin()] : + [new ReactRefreshPlugin()]), + resolve: { + extensions: [ + ".js", + ".jsx", + ".json", + ], + }, +}; + +export default () => { + config.mode = isProduction ? + "production" : + "development"; + + return config; +}; diff --git a/components/log-viewer-webui/package-lock.json b/components/log-viewer-webui/package-lock.json new file mode 100644 index 000000000..4ca32e8ff --- /dev/null +++ b/components/log-viewer-webui/package-lock.json @@ -0,0 +1,351 @@ +{ + "name": "log-viewer-webui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui", + "version": "0.1.0", + "license": "Apache-2.0", + "devDependencies": { + "concurrently": "^8.2.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/components/log-viewer-webui/package.json b/components/log-viewer-webui/package.json new file mode 100644 index 000000000..d019ba21b --- /dev/null +++ b/components/log-viewer-webui/package.json @@ -0,0 +1,22 @@ +{ + "name": "log-viewer-webui", + "version": "0.1.0", + "description": "", + "scripts": { + "client:lint:check": "cd client && npm run lint:check", + "client:lint:fix": "cd client && npm run lint:fix", + "client:start": "cd client && npm start", + "init": "npm i && (cd client && npm i) && (cd server && npm i)", + "lint:check": "npm run client:lint:check && npm run server:lint:check", + "lint:fix": "npm run client:lint:fix && npm run server:lint:fix", + "server:lint:check": "cd server && npm run lint:check", + "server:lint:fix": "cd server && npm run lint:fix", + "server:start": "cd server && npm start", + "start": "concurrently \"npm run client:start\" \"npm run server:start\"" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "devDependencies": { + "concurrently": "^8.2.2" + } +} diff --git a/components/log-viewer-webui/server/.env b/components/log-viewer-webui/server/.env new file mode 100644 index 000000000..95e3054a8 --- /dev/null +++ b/components/log-viewer-webui/server/.env @@ -0,0 +1,8 @@ +CLIENT_DIR=../client/dist +STREAMS_DATA_DIR=../../../build/clp-package/var/data/streams +LOG_VIEWER_DIR=../yscope-log-viewer/dist + +HOST=localhost +PORT=3000 +CLP_DB_USER=clp-user +CLP_DB_PASS= diff --git a/components/log-viewer-webui/server/.gitignore b/components/log-viewer-webui/server/.gitignore new file mode 100644 index 000000000..e19dcc6a4 --- /dev/null +++ b/components/log-viewer-webui/server/.gitignore @@ -0,0 +1,5 @@ + # Local development +.env.local + +# Testing +/.tap diff --git a/components/log-viewer-webui/server/package-lock.json b/components/log-viewer-webui/server/package-lock.json new file mode 100644 index 000000000..d16171d80 --- /dev/null +++ b/components/log-viewer-webui/server/package-lock.json @@ -0,0 +1,10659 @@ +{ + "name": "log-viewer-webui-server", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui-server", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@fastify/mongodb": "^8.0.0", + "@fastify/mysql": "^4.3.0", + "@fastify/static": "^7.0.4", + "@msgpack/msgpack": "^3.0.0-beta2", + "dotenv": "^16.4.5", + "fastify": "^4.28.0", + "fastify-plugin": "^4.5.1", + "http-status-codes": "^2.3.0", + "pino-pretty": "^11.2.1" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.8", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "eslint-config-yscope": "latest", + "nodemon": "^3.1.3", + "tap": "^19.2.5" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", + "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.8.tgz", + "integrity": "sha512-Po3VLMN7fJtv0nsOjBDSbO1J71UhzShE9MuOSkWEV9IZQXzhZklYtzKZ8ZD/Ij3a0JBv1AG3Ny2L3jvAHQVOGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.25.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@fastify/mongodb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@fastify/mongodb/-/mongodb-8.0.0.tgz", + "integrity": "sha512-IDw/wWpdc53+Y5sPpMg+ek71HOIVuz8NoD2GlfIOcvGE/lYdrZvnFQxqJcaZtlwPZ7YflDDkIu5aNkCPWdZQ0Q==", + "license": "MIT", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mongodb": "^6.0.0" + } + }, + "node_modules/@fastify/mysql": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/mysql/-/mysql-4.3.0.tgz", + "integrity": "sha512-eVx5/PyMmoBWp3hTaqdvXiZdo8YnKsAx3k/8AEXgI/MjUbgcn8YrSdy8eHSpCL3YZtBhD/2vLpOXFFciyqlWjQ==", + "license": "MIT", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mysql2": "^3.9.7" + } + }, + "node_modules/@fastify/send": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", + "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.1", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "2.0.0", + "mime": "^3.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "fastq": "^1.17.0", + "glob": "^10.3.4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ts-node-temp-fork-for-pr-2009": { + "version": "10.9.7", + "resolved": "https://registry.npmjs.org/@isaacs/ts-node-temp-fork-for-pr-2009/-/ts-node-temp-fork-for-pr-2009-10.9.7.tgz", + "integrity": "sha512-9f0bhUr9TnwwpgUhEpr3FjxSaH/OHaARkE2F9fM0lS4nIs2GNerrvGwQz493dk0JKlTaGYVrKbq36vA/whZ34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node14": "*", + "@tsconfig/node16": "*", + "@tsconfig/node18": "*", + "@tsconfig/node20": "*", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=4.2" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/@isaacs/ts-node-temp-fork-for-pr-2009/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.0.0-beta2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz", + "integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==", + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", + "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", + "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.1.tgz", + "integrity": "sha512-c5c2C8Mos5tTQd+NWpqwEu7VT6SSRooAguFPMj1cp2RkTYl1ynKoXo8MWy3k4rkbzoeYHrqC2UlUzsroAN7wtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.8.1.tgz", + "integrity": "sha512-k1Eb6rcjMP+mmjvj+vd9y5KUdWn1OBkkPLHXhsrHt5lCDFZxJEs0aVQzE5lpYrtVZVkpc5esTtss/cPJux0lfA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^1.8.1", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.8.1.tgz", + "integrity": "sha512-4+40H3lHYTN8OWz+US8CamVkO+2hxNLp9+CAjorI7top/lHqemhpJvKA1LD9Uh+WMY9DYWiWpL2+SZ2wAXY9fQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@tapjs/after": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-1.1.31.tgz", + "integrity": "sha512-531NkYOls9PvqfnLsEDRzIWwjynoFRbUVq7pTYuA3PRIw4Ka7jA9uUjILeUurcWjaHrQNzUua0jj/Yu94f6YYw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "is-actual-promise": "^1.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/after-each": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-2.0.8.tgz", + "integrity": "sha512-btkpQ/BhmRyG50rezduxEZb3pMJblECvTQa41+U2ln2te1prDTlllHlpq4lOjceUksl8KFF1avDqcBqIqPzneQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "function-loop": "^4.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/asserts": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-2.0.8.tgz", + "integrity": "sha512-57VrI0p2kAqfgHHUwowDvd31eTfDHw3HO4FSSVUCvngPGWa96R6eH9gXa9fNig4qIp4Dup+nI7gJlJfU0R80SA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/stack": "2.0.1", + "is-actual-promise": "^1.0.1", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/before": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-2.0.8.tgz", + "integrity": "sha512-22ZdGSn/zOKf8J8cb3yfw5R4I/ozdHEDKL8lBWon/zsxxMMvaRTgOtFXEjb4RE+5SDrqQ4NM7ZRYPGhE7T97dw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "is-actual-promise": "^1.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/before-each": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-2.0.8.tgz", + "integrity": "sha512-Xjgk8/fuP7iFa5CYjFDl05p5PZGRe//VyHJNuYNzWpF1K9PNMtVdlmwplfpFmbrNrw/bIPq7R6LuiPmTBgzuOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "function-loop": "^4.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/chdir": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@tapjs/chdir/-/chdir-1.1.4.tgz", + "integrity": "sha512-axXkT5kWp2/X8l6inKyrqzUhqgvsgrWI8/0xLAdmirpFZ8H6gFxrl763Ozdm27EAmkLnnnWgFITPqUQCuB/tMA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/config": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-3.1.6.tgz", + "integrity": "sha512-5gkDMSLXL5798bbCdX4RdLpB4OUQeu9TXftzKmL1+1T2xbcd4q7zfDnCfOB9zTk50x2f04+4h6Q7Z1NcSKIspg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/core": "2.1.6", + "@tapjs/test": "2.2.4", + "chalk": "^5.2.0", + "jackspeak": "^3.1.2", + "polite-json": "^4.0.1", + "tap-yaml": "2.2.2", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6", + "@tapjs/test": "2.2.4" + } + }, + "node_modules/@tapjs/config/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@tapjs/core": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-2.1.6.tgz", + "integrity": "sha512-NYMp0bl52DxXfcLmivMKvOIE14aaB9qJjdHeUbs6GZ9yxgD5w0yeiOT+gWEL+1PzZgGWRxSFEpghID1YfXAc4w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/processinfo": "^3.1.8", + "@tapjs/stack": "2.0.1", + "@tapjs/test": "2.2.4", + "async-hook-domain": "^4.0.1", + "diff": "^5.2.0", + "is-actual-promise": "^1.0.1", + "minipass": "^7.0.4", + "signal-exit": "4.1", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/@tapjs/error-serdes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-2.0.1.tgz", + "integrity": "sha512-P+M4rtcfkDsUveKKmoRNF+07xpbPnRY5KrstIUOnyn483clQ7BJhsnWr162yYNCsyOj4zEfZmAJI1f8Bi7h/ZA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/filter": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-2.0.8.tgz", + "integrity": "sha512-/ps6nOS3CTh1WLfCjJnU7tS4PH4KFgEasFSVPCIFN+BasyoqDapzj4JKIlzQvppZOGTQadKH3wUakafZl7uz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/fixture": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-2.0.8.tgz", + "integrity": "sha512-LJnjeAMSozPFXzu+wQw2HJsjA9djHbTcyeMnsgiRL/Q8ffcLqAawV3SN6XKdDLdWYUg3e1fXhHspnbsouZj+xA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "mkdirp": "^3.0.0", + "rimraf": "^5.0.5" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/fixture/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/intercept": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-2.0.8.tgz", + "integrity": "sha512-OF2Q35jtZ20bwV4hRNoca7vqIrzPFR3JR25G2rGru+fgPmq4heN0RLoh0d1O34AbrtXqra2lXkacMB/DPgb01A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/stack": "2.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/mock": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-2.1.6.tgz", + "integrity": "sha512-bNXKrjg/r+i/gfKij5Oo/5Md2DvGNHPSRCHQmjz3VQjpyxqK7S1FGcR0kyqJ8Nof6Wc8yIhpNOCuibj19200IQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/stack": "2.0.1", + "resolve-import": "^1.4.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/node-serialize": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-2.0.8.tgz", + "integrity": "sha512-92oqhkmIz5wr0yRs1CPQfim5JSwHPSmoDWnQmJlYUZsY1OYgYouQm3ifnPkqK/9hJpVYzlZEQmefxehxbs2WNQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/error-serdes": "2.0.1", + "@tapjs/stack": "2.0.1", + "tap-parser": "16.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/processinfo": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.8.tgz", + "integrity": "sha512-FIriEB+qqArPhmVYc1PZwRHD99myRdl7C9Oe/uts04Q2LOxQ5MEmqP9XOP8vVYzpDOYwmL8OmL6eOYt9eZlQKQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "pirates": "^4.0.5", + "process-on-spawn": "^1.0.0", + "signal-exit": "^4.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=16.17" + } + }, + "node_modules/@tapjs/reporter": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-2.0.8.tgz", + "integrity": "sha512-tZn5ZHIrFwjbi59djtdXHBwgSIZSBXdJpz2i9CZ9HEC1nFhWtIr2Jczvrz4ScfixUgA0GNFirz+q+9iA4IFMvw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/config": "3.1.6", + "@tapjs/stack": "2.0.1", + "chalk": "^5.2.0", + "ink": "^4.4.1", + "minipass": "^7.0.4", + "ms": "^2.1.3", + "patch-console": "^2.0.0", + "prismjs-terminal": "^1.2.3", + "react": "^18.2.0", + "string-length": "^6.0.0", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/reporter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@tapjs/run": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-2.1.7.tgz", + "integrity": "sha512-Hk41E68f1x4eLBm6Rrxx4ARzZzrjwaLbKThb16+f3bGYiajmqAvBdeyNEoQpEWmW+Sv2HSlueOk2SS2P4fyetg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/before": "2.0.8", + "@tapjs/config": "3.1.6", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/reporter": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/test": "2.2.4", + "c8": "^9.1.0", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "foreground-child": "^3.1.1", + "glob": "^10.3.16", + "minipass": "^7.0.4", + "mkdirp": "^3.0.1", + "opener": "^1.5.2", + "pacote": "^17.0.6", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.5", + "semver": "^7.6.0", + "signal-exit": "^4.1.0", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0", + "which": "^4.0.0" + }, + "bin": { + "tap-run": "dist/esm/index.js" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/run/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@tapjs/run/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@tapjs/run/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/run/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tapjs/run/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tapjs/snapshot": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-2.0.8.tgz", + "integrity": "sha512-L0vtqWKkgnQt/XNQkvHOme9Np7ffteCNf1P0F9mz2YiJion4er1nv6pZuJoKVxXFQsbNd2k+LGyx0Iw+bIzwFg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "is-actual-promise": "^1.0.1", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/spawn": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-2.0.8.tgz", + "integrity": "sha512-vCYwynIYJNijY87uHFANe+gCu9rdGoe4GOBmghl6kwDy7eISmcN/FW5TlmrjePMNhTvrDMeYqOIAzqh3WRYmPA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/stack": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-2.0.1.tgz", + "integrity": "sha512-3rKbZkRkLeJl9ilV/6b80YfI4C4+OYf7iEz5/d0MIVhmVvxv0ttIy5JnZutAc4Gy9eRp5Ne5UTAIFOVY5k36cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/stdin": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-2.0.8.tgz", + "integrity": "sha512-tW/exLXuDqjtH2wjptiPHXBahkdSyoppxDY56l9MG4tiz66dMN6NTCZFvQxp7+3t+lsQKqJp/74z8T/ayp+vZA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/test": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-2.2.4.tgz", + "integrity": "sha512-QIgq2BhMpwO9SN8I0qlwZYXAllO4xWCfJ0MgAGhc+J7p69B5p9dDNPmyOreHeXWMmk6VlNj3oWveoXb5Zn9xZQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7", + "@tapjs/after": "1.1.31", + "@tapjs/after-each": "2.0.8", + "@tapjs/asserts": "2.0.8", + "@tapjs/before": "2.0.8", + "@tapjs/before-each": "2.0.8", + "@tapjs/chdir": "1.1.4", + "@tapjs/filter": "2.0.8", + "@tapjs/fixture": "2.0.8", + "@tapjs/intercept": "2.0.8", + "@tapjs/mock": "2.1.6", + "@tapjs/node-serialize": "2.0.8", + "@tapjs/snapshot": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/typescript": "1.4.13", + "@tapjs/worker": "2.0.8", + "glob": "^10.3.16", + "jackspeak": "^3.1.2", + "mkdirp": "^3.0.0", + "package-json-from-dist": "^1.0.0", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.5", + "sync-content": "^1.0.1", + "tap-parser": "16.0.1", + "tshy": "^1.14.0", + "typescript": "5.4", + "walk-up-path": "^3.0.1" + }, + "bin": { + "generate-tap-test-class": "dist/esm/build.mjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/test/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/test/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@tapjs/typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-1.4.13.tgz", + "integrity": "sha512-MNs7zlhM6G3pNUIjkKXDxgNCwCGZt2bUCGtVunSTDVIrKiUlHAl4QSjQ1oTjumHlCi9gFIWiwFAvpHekzFti0w==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/worker": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-2.0.8.tgz", + "integrity": "sha512-AySf2kV6OHvwgD3DrLdT2az2g4hRdoRtKsFCLdZo3jOoKte+ft/IQJEnOW7CPT0RYUskS3elv6eabYgSyTH4tg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tsconfig/node14": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.2.tgz", + "integrity": "sha512-1vncsbfCZ3TBLPxesRYz02Rn7SNJfbLoDVkcZ7F/ixOV6nwxwgdhD1mdPcc5YQ413qBJ8CvMxXMFfJ7oawjo7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", + "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "22.7.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz", + "integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aggregate-error/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-hook-domain": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-4.0.1.tgz", + "integrity": "sha512-bSktexGodAjfHWIrSrrqxqWzf1hWBZBpmPNZv+TYUMyWa2eoefFc6q6H1+KtdHYSz35lrhWdmXt/XK9wNEZvww==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "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", + "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", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "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", + "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", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", + "integrity": "sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-yscope": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/eslint-config-yscope/-/eslint-config-yscope-0.0.31.tgz", + "integrity": "sha512-cA6sS3G4Ydoht/CvST7C7moqJO+NiKl0InQtkX5YKocXAQE6KZ/VW9/kORdNnpIGCLwkqvMOa7y4XNJZnTfubw==", + "dev": true, + "peerDependencies": { + "@stylistic/eslint-plugin-js": "^1.6.2", + "@stylistic/eslint-plugin-jsx": "^1.6.2", + "@stylistic/eslint-plugin-plus": "^1.6.2", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import-newlines": "^1.4.0", + "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-no-autofix": "^1.2.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-simple-import-sort": "^12.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "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-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.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": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import-newlines": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.4.0.tgz", + "integrity": "sha512-+Cz1x2xBLtI9gJbmuYEpvY7F8K75wskBmJ7rk4VRObIJo+jklUJaejFJgtnWeL0dCFWabGEkhausrikXaNbtoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "import-linter": "lib/index.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.11.0.tgz", + "integrity": "sha512-d12JHJDPNo7IFwTOAItCeJY1hcqoIxE0lHA8infQByLilQ9xkqrRa6laWCnsuCrf+8rUnvxXY1XuTbibRBNylA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-no-autofix": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-autofix/-/eslint-plugin-no-autofix-1.2.3.tgz", + "integrity": "sha512-JFSYe82Da2A8Krh+Gfq7+3X2pchTScKgmrlMKIA4HmV6t5xGBF/kgjiFL3YTWRQXQ0NB9eOqpcxh6SuLtQUFjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0", + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "eslint": ">= 5.12.1" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", + "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-to-array": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-2.0.3.tgz", + "integrity": "sha512-f/qE2gImHRa4Cp2y1stEOSgw8wTFyUdVJX7G//bMwbaV9JqISFxg99NbmVQeP7YLnDUZ2un851jlaDrlpmGehQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.28.1.tgz", + "integrity": "sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "license": "MIT" + }, + "node_modules/fastify/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-loop": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-4.0.0.tgz", + "integrity": "sha512-f34iQBedYF3XcI93uewZZOnyscDragxgTK/eTvVB74k3fCD0ZorOi5BV9GS4M8rz/JoNi0Kl3qX5Y9MH3S/CLQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ink": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-4.4.1.tgz", + "integrity": "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.1.3", + "ansi-escapes": "^6.0.0", + "auto-bind": "^5.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^3.1.0", + "code-excerpt": "^4.0.0", + "indent-string": "^5.0.0", + "is-ci": "^3.0.1", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lodash": "^4.17.21", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^6.0.0", + "stack-utils": "^2.0.6", + "string-width": "^5.1.2", + "type-fest": "^0.12.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0", + "ws": "^8.12.0", + "yoga-wasm-web": "~0.3.3" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ink/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-actual-promise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-actual-promise/-/is-actual-promise-1.0.2.tgz", + "integrity": "sha512-xsFiO1of0CLsQnPZ1iXHNTyR9YszOeWKYv+q6n8oSFW3ipooFJ1j1lbRMgiMCr+pp2gLruESI4zb5Ak6eK5OnQ==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", + "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", + "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-json-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.2.tgz", + "integrity": "sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mongodb": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", + "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.3.tgz", + "integrity": "sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-registry-fetch": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", + "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^1.1.0", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.7.tgz", + "integrity": "sha512-sgvnoUMlkv9xHwDUKjKQFXVyUi8dtJGKp3vg6sYy+TxbDic5RjZCHF3ygv0EJgNRZ2GfRONjlKPUfokJ9lDpwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", + "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz", + "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", + "license": "MIT" + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/polite-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-4.0.1.tgz", + "integrity": "sha512-8LI5ZeCPBEb4uBbcYKNVwk4jgqNx1yHReWoW4H4uUihWlSqZsUDfSITrRhjliuPgxsNPFhNSudGO2Zu4cbWinQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prismjs-terminal": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prismjs-terminal/-/prismjs-terminal-1.2.3.tgz", + "integrity": "sha512-xc0zuJ5FMqvW+DpiRkvxURlz98DdfDsZcFHdO699+oL+ykbFfgI7O4VDEgUyc07BSL2NHl3zdb8m/tZ/aaqUrw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "chalk": "^5.2.0", + "prismjs": "^1.29.0", + "string-length": "^6.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/prismjs-terminal/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "peerDependencies": { + "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", + "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/read-package-json": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", + "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-import": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-1.4.6.tgz", + "integrity": "sha512-CIw9e64QcKcCFUj9+KxUCJPy8hYofv6eVfo3U9wdhCm2E4IjvFnZ6G4/yIC4yP3f11+h6uU5b3LdS7O64LgqrA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^10.3.3", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "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", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/slice-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", + "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", + "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sync-content": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-1.0.2.tgz", + "integrity": "sha512-znd3rYiiSxU3WteWyS9a6FXkTA/Wjk8WQsOyzHbineeL837dLn3DA4MRhsIX3qGcxDMH6+uuFV4axztssk7wEQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^10.2.6", + "mkdirp": "^3.0.1", + "path-scurry": "^1.9.2", + "rimraf": "^5.0.1" + }, + "bin": { + "sync-content": "dist/mjs/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sync-content/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tap": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/tap/-/tap-19.2.5.tgz", + "integrity": "sha512-Mz7MznUuKCqrN9dr0s8REt6zLg6WLNrvGXwDSaUyPO73dpXXjakYA7YVKRWu6TBnj7NsSYKuHXpQFROlqZ2KTg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/after-each": "2.0.8", + "@tapjs/asserts": "2.0.8", + "@tapjs/before": "2.0.8", + "@tapjs/before-each": "2.0.8", + "@tapjs/chdir": "1.1.4", + "@tapjs/core": "2.1.6", + "@tapjs/filter": "2.0.8", + "@tapjs/fixture": "2.0.8", + "@tapjs/intercept": "2.0.8", + "@tapjs/mock": "2.1.6", + "@tapjs/node-serialize": "2.0.8", + "@tapjs/run": "2.1.7", + "@tapjs/snapshot": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/test": "2.2.4", + "@tapjs/typescript": "1.4.13", + "@tapjs/worker": "2.0.8", + "resolve-import": "^1.4.5" + }, + "bin": { + "tap": "dist/esm/run.mjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tap-parser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-16.0.1.tgz", + "integrity": "sha512-vKianJzSSzLkJ3bHBwzvZDDRi9yGMwkRANJxwPAjAue50owB8rlluYySmTN4tZVH0nsh6stvrQbg9kuCL5svdg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "events-to-array": "^2.0.3", + "tap-yaml": "2.2.2" + }, + "bin": { + "tap-parser": "bin/cmd.cjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/tap-yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-2.2.2.tgz", + "integrity": "sha512-MWG4OpAKtNoNVjCz/BqlDJiwTM99tiHRhHPS4iGOe1ZS0CgM4jSFH92lthSFvvy4EdDjQZDV7uYqUFlU9JuNhw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "yaml": "^2.4.1", + "yaml-types": "^0.3.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/tcompare": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", + "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "diff": "^5.2.0", + "react-element-to-jsx-string": "^15.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/trivial-deferred": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trivial-deferred/-/trivial-deferred-2.0.0.tgz", + "integrity": "sha512-iGbM7X2slv9ORDVj2y2FFUq3cP/ypbtu2nQ8S38ufjL0glBABvmR9pTdsib1XtS2LUhhLMbelaBUaf/s5J3dSw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tshy": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tshy/-/tshy-1.18.0.tgz", + "integrity": "sha512-FQudIujBazHRu7CVPHKQE9/Xq1Wc7lezxD/FCnTXx2PTcnoSN32DVpb/ZXvzV2NJBTDB3XKjqX8Cdm+2UK1DlQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "foreground-child": "^3.1.1", + "minimatch": "^9.0.4", + "mkdirp": "^3.0.1", + "polite-json": "^5.0.0", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.1", + "sync-content": "^1.0.2", + "typescript": "5", + "walk-up-path": "^3.0.1" + }, + "bin": { + "tshy": "dist/esm/index.js" + }, + "engines": { + "node": "16 >=16.17 || 18 >=18.15.0 || >=20.6.1" + } + }, + "node_modules/tshy/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/tshy/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tshy/node_modules/polite-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-5.0.0.tgz", + "integrity": "sha512-OLS/0XeUAcE8a2fdwemNja+udKgXNnY6yKVIXqAD2zVRx1KvY6Ato/rZ2vdzbxqYwPW0u6SCNC/bAMPNzpzxbw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tshy/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "license": "MIT", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yaml-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.3.0.tgz", + "integrity": "sha512-i9RxAO/LZBiE0NJUy9pbN5jFz5EasYDImzRkj8Y81kkInTi1laia3P3K/wlMKzOxFQutZip8TejvQP/DwgbU7A==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 16", + "npm": ">= 7" + }, + "peerDependencies": { + "yaml": "^2.3.0" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/components/log-viewer-webui/server/package.json b/components/log-viewer-webui/server/package.json new file mode 100644 index 000000000..f952b6c86 --- /dev/null +++ b/components/log-viewer-webui/server/package.json @@ -0,0 +1,48 @@ +{ + "name": "log-viewer-webui-server", + "version": "0.1.0", + "description": "", + "main": "src/main.js", + "scripts": { + "lint:check": "npx eslint --no-eslintrc --config package.json src", + "lint:fix": "npx eslint --fix --no-eslintrc --config package.json src", + "prod": "NODE_ENV=production node src/main.js", + "start": "NODE_ENV=development nodemon src/main.js", + "test": "NODE_ENV=test tap" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "type": "module", + "dependencies": { + "@fastify/mongodb": "^8.0.0", + "@fastify/mysql": "^4.3.0", + "@fastify/static": "^7.0.4", + "fastify-plugin": "^4.5.1", + "@msgpack/msgpack": "^3.0.0-beta2", + "dotenv": "^16.4.5", + "fastify": "^4.28.0", + "http-status-codes": "^2.3.0", + "pino-pretty": "^11.2.1" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.8", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "eslint-config-yscope": "latest", + "nodemon": "^3.1.3", + "tap": "^19.2.5" + }, + "eslintConfig": { + "extends": [ + "yscope/common" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false, + "babelOptions": { + "plugins": [ + "@babel/plugin-syntax-import-attributes" + ] + } + } + } +} diff --git a/components/log-viewer-webui/server/settings.json b/components/log-viewer-webui/server/settings.json new file mode 100644 index 000000000..9e749e144 --- /dev/null +++ b/components/log-viewer-webui/server/settings.json @@ -0,0 +1,15 @@ +{ + "SqlDbHost": "localhost", + "SqlDbPort": 3306, + "SqlDbName": "clp-db", + "SqlDbQueryJobsTableName": "query_jobs", + "MongoDbHost": "localhost", + "MongoDbPort": 27017, + "MongoDbName": "clp-query-results", + "MongoDbStreamFilesCollectionName": "stream-files", + + "ClientDir": "../client/dist", + "StreamFilesDir": "../../../build/clp-package/var/data/streams", + "StreamTargetUncompressedSize": 134217728, + "LogViewerDir": "../yscope-log-viewer/dist" +} diff --git a/components/log-viewer-webui/server/src/DbManager.js b/components/log-viewer-webui/server/src/DbManager.js new file mode 100644 index 000000000..e1ec00812 --- /dev/null +++ b/components/log-viewer-webui/server/src/DbManager.js @@ -0,0 +1,277 @@ +import fastifyPlugin from "fastify-plugin"; + +import fastifyMongo from "@fastify/mongodb"; +import fastifyMysql from "@fastify/mysql"; +import {encode as msgpackEncode} from "@msgpack/msgpack"; + +import {sleep} from "./utils.js"; + + +/** + * Interval in milliseconds for polling the completion status of a job. + */ +const JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS = 0.5; + +/** + * Enum of the `query_jobs` table's column names. + * + * @enum {string} + */ +const QUERY_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ + ID: "id", + STATUS: "status", + TYPE: "type", + JOB_CONFIG: "job_config", +}); + +/* eslint-disable sort-keys */ +let enumQueryJobStatus; +/** + * Enum of job statuses, matching the `QueryJobStatus` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_STATUS = Object.freeze({ + PENDING: (enumQueryJobStatus = 0), + RUNNING: ++enumQueryJobStatus, + SUCCEEDED: ++enumQueryJobStatus, + FAILED: ++enumQueryJobStatus, + CANCELLING: ++enumQueryJobStatus, + CANCELLED: ++enumQueryJobStatus, +}); +/* eslint-enable sort-keys */ + +/** + * List of states that indicate the job is either pending or in progress. + */ +const QUERY_JOB_STATUS_WAITING_STATES = Object.freeze([ + QUERY_JOB_STATUS.PENDING, + QUERY_JOB_STATUS.RUNNING, + QUERY_JOB_STATUS.CANCELLING, +]); + +/* eslint-disable sort-keys */ +let enumQueryType; +/** + * Enum of job types, matching the `QueryJobType` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_TYPE = Object.freeze({ + SEARCH_OR_AGGREGATION: (enumQueryType = 0), + EXTRACT_IR: ++enumQueryType, + EXTRACT_JSON: ++enumQueryType, +}); +/* eslint-enable sort-keys */ + +/** + * List of valid extract job types. + */ +const EXTRACT_JOB_TYPES = Object.freeze([ + QUERY_JOB_TYPE.EXTRACT_IR, + QUERY_JOB_TYPE.EXTRACT_JSON, +]); + +/** + * Class to manage connections to the jobs database (MySQL) and results cache (MongoDB). + */ +class DbManager { + /** + * @type {import("fastify").FastifyInstance | + * {mysql: import("@fastify/mysql").MySQLPromisePool} | + * {mongo: import("@fastify/mongodb").FastifyMongoObject}} + */ + #fastify; + + /** + * @type {import("@fastify/mysql").PromisePool} + */ + #mysqlConnectionPool; + + /** + * @type {import("mongodb").Collection} + */ + #streamFilesCollection; + + #queryJobsTableName; + + /** + * @param {import("fastify").FastifyInstance} app + * @param {object} dbConfig + * @param {object} dbConfig.mysqlConfig + * @param {object} dbConfig.mongoConfig + */ + constructor (app, dbConfig) { + this.#fastify = app; + this.#initMySql(dbConfig.mysqlConfig); + this.#initMongo(dbConfig.mongoConfig); + } + + /** + * Submits a stream extraction job to the scheduler and waits for it to finish. + * + * @param {number} jobType + * @param {number} logEventIdx + * @param {string} streamId + * @param {number} targetUncompressedSize + * @return {Promise} The ID of the job or null if an error occurred. + */ + async submitAndWaitForExtractStreamJob ({ + jobType, + logEventIdx, + streamId, + targetUncompressedSize, + }) { + let jobConfig; + if (QUERY_JOB_TYPE.EXTRACT_IR === jobType) { + jobConfig = { + file_split_id: null, + msg_ix: logEventIdx, + orig_file_id: streamId, + target_uncompressed_size: targetUncompressedSize, + }; + } else if (QUERY_JOB_TYPE.EXTRACT_JSON === jobType) { + jobConfig = { + archive_id: streamId, + target_chunk_size: targetUncompressedSize, + }; + } + + let jobId; + try { + const [result] = await this.#mysqlConnectionPool.query( + `INSERT INTO ${this.#queryJobsTableName} (job_config, type) + VALUES (?, ?)`, + [ + Buffer.from(msgpackEncode(jobConfig)), + jobType, + ] + ); + + ({insertId: jobId} = result); + await this.#awaitJobCompletion(jobId); + } catch (e) { + this.#fastify.log.error(e); + + return null; + } + + return jobId; + } + + /** + * Gets the metadata for the extracted stream that has the given streamId and contains the + * given logEventIdx. + * + * @param {string} streamId + * @param {number} logEventIdx + * @return {Promise} A promise that resolves to the extracted stream's metadata. + */ + async getExtractedStreamFileMetadata (streamId, logEventIdx) { + return await this.#streamFilesCollection.findOne({ + orig_file_id: streamId, + begin_msg_ix: {$lte: logEventIdx}, + end_msg_ix: {$gt: logEventIdx}, + }); + } + + /** + * Initializes the MySQL plugin. + * + * @param {object} config + * @param {string} config.user + * @param {string} config.password + * @param {string} config.host + * @param {number} config.port + * @param {string} config.database + * @param {string} config.queryJobsTableName + */ + #initMySql (config) { + this.#fastify.register(fastifyMysql, { + promise: true, + connectionString: `mysql://${config.user}:${config.password}@${config.host}:` + + `${config.port}/${config.database}`, + }).after(async (err) => { + if (err) { + throw err; + } + this.#mysqlConnectionPool = this.#fastify.mysql.pool; + this.#queryJobsTableName = config.queryJobsTableName; + }); + } + + /** + * Initializes the MongoDB plugin. + * + * @param {object} config + * @param {string} config.host + * @param {number} config.port + * @param {string} config.database + * @param {string} config.StreamFilesCollectionName + */ + #initMongo (config) { + this.#fastify.register(fastifyMongo, { + forceClose: true, + url: `mongodb://${config.host}:${config.port}/${config.database}`, + }).after((err) => { + if (err) { + throw err; + } + this.#streamFilesCollection = + this.#fastify.mongo.db.collection(config.streamFilesCollectionName); + }); + } + + /** + * Waits for the job with the given ID to finish. + * + * @param {number} jobId + * @throws {Error} If there's an error querying the job's status, the job is not found in the + * database, the job was cancelled, or it exited with an unexpected status. + */ + async #awaitJobCompletion (jobId) { + while (true) { + let rows; + try { + const [queryRows] = await this.#mysqlConnectionPool.query( + ` + SELECT ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + FROM ${this.#queryJobsTableName} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? + `, + jobId, + ); + + rows = queryRows; + } catch (e) { + throw new Error(`Failed to query status for job ${jobId} - ${e}`); + } + if (0 === rows.length) { + throw new Error(`Job ${jobId} not found in database.`); + } + const status = rows[0][QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS]; + + if (false === QUERY_JOB_STATUS_WAITING_STATES.includes(status)) { + if (QUERY_JOB_STATUS.CANCELLED === status) { + throw new Error(`Job ${jobId} was cancelled.`); + } else if (QUERY_JOB_STATUS.SUCCEEDED !== status) { + throw new Error(`Job ${jobId} exited with unexpected status=${status}: ` + + `${Object.keys(QUERY_JOB_STATUS)[status]}.`); + } + break; + } + + await sleep(JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS); + } + } +} + +export { + EXTRACT_JOB_TYPES, + QUERY_JOB_TYPE, +}; +export default fastifyPlugin(async (app, options) => { + await app.decorate("dbManager", new DbManager(app, options)); +}); diff --git a/components/log-viewer-webui/server/src/app.js b/components/log-viewer-webui/server/src/app.js new file mode 100644 index 000000000..5d351fd39 --- /dev/null +++ b/components/log-viewer-webui/server/src/app.js @@ -0,0 +1,53 @@ +import fastify from "fastify"; +import process from "node:process"; + +import settings from "../settings.json" with {type: "json"}; +import DbManager from "./DbManager.js"; +import exampleRoutes from "./routes/example.js"; +import queryRoutes from "./routes/query.js"; +import staticRoutes from "./routes/static.js"; + + +/** + * Creates the Fastify app with the given options. + * + * @param {object} props + * @param {import("fastify").FastifyServerOptions} props.fastifyOptions + * @param {string} props.sqlDbUser + * @param {string} props.sqlDbPass + * @return {Promise} + */ +const app = async ({ + fastifyOptions, + sqlDbUser, + sqlDbPass, +}) => { + const server = fastify(fastifyOptions); + + if ("test" !== process.env.NODE_ENV) { + await server.register(DbManager, { + mysqlConfig: { + database: settings.SqlDbName, + host: settings.SqlDbHost, + password: sqlDbPass, + port: settings.SqlDbPort, + queryJobsTableName: settings.SqlDbQueryJobsTableName, + user: sqlDbUser, + }, + mongoConfig: { + database: settings.MongoDbName, + host: settings.MongoDbHost, + streamFilesCollectionName: settings.MongoDbStreamFilesCollectionName, + port: settings.MongoDbPort, + }, + }); + } + + await server.register(staticRoutes); + await server.register(exampleRoutes); + await server.register(queryRoutes); + + return server; +}; + +export default app; diff --git a/components/log-viewer-webui/server/src/app.test.js b/components/log-viewer-webui/server/src/app.test.js new file mode 100644 index 000000000..3a19208f7 --- /dev/null +++ b/components/log-viewer-webui/server/src/app.test.js @@ -0,0 +1,29 @@ +import httpStatusCodes from "http-status-codes"; +import {test} from "tap"; + +import app from "./app.js"; + + +test("Tests the example routes", async (t) => { + const server = await app({}); + t.teardown(() => server.close()); + + let resp = await server.inject({ + method: "GET", + url: "/example/get/Alice", + }); + + t.equal(resp.statusCode, httpStatusCodes.OK); + t.match(JSON.parse(resp.body), {msg: String}); + + resp = await server.inject({ + method: "POST", + url: "/example/post", + payload: {name: "Bob"}, + }); + t.equal(resp.statusCode, httpStatusCodes.OK); + t.match(JSON.parse(resp.body), {msg: String}); +}); + +// eslint-disable-next-line no-warning-comments +// TODO: Add tests for `query` routes. diff --git a/components/log-viewer-webui/server/src/main.js b/components/log-viewer-webui/server/src/main.js new file mode 100644 index 000000000..c3b7ef5cf --- /dev/null +++ b/components/log-viewer-webui/server/src/main.js @@ -0,0 +1,74 @@ +import dotenv from "dotenv"; +import process from "node:process"; + +import app from "./app.js"; + + +/** + * Parses environment variables into config values for the application. + * + * @return {{CLP_DB_USER: string, CLP_DB_PASS: string, HOST: string, PORT: string}} + * @throws {Error} if any required environment variable is undefined. + */ +const parseEnvVars = () => { + dotenv.config({ + path: [ + ".env.local", + ".env", + ], + }); + + /* eslint-disable sort-keys */ + const { + CLP_DB_USER, CLP_DB_PASS, HOST, PORT, + } = process.env; + const envVars = { + CLP_DB_USER, CLP_DB_PASS, HOST, PORT, + }; + /* eslint-enable sort-keys */ + + // Check for mandatory environment variables + for (const [key, value] of Object.entries(envVars)) { + if ("undefined" === typeof value) { + throw new Error(`Environment variable ${key} must be defined.`); + } + } + + return envVars; +}; + +/** + * Sets up and runs the server. + */ +const main = async () => { + const envToLogger = { + development: { + transport: { + target: "pino-pretty", + }, + }, + production: true, + test: false, + }; + + const envVars = parseEnvVars(); + const server = await app({ + fastifyOptions: { + logger: envToLogger[process.env.NODE_ENV] ?? true, + }, + sqlDbPass: envVars.CLP_DB_PASS, + sqlDbUser: envVars.CLP_DB_USER, + }); + + try { + await server.listen({host: envVars.HOST, port: Number(envVars.PORT)}); + } catch (e) { + server.log.error(e); + process.exit(1); + } +}; + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/components/log-viewer-webui/server/src/routes/example.js b/components/log-viewer-webui/server/src/routes/example.js new file mode 100644 index 000000000..fb4aa31c7 --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/example.js @@ -0,0 +1,18 @@ +/** + * Creates example routes. + * + * @param {import("fastify").FastifyInstance} fastify + * @param {import("fastify").FastifyPluginOptions} options + * @return {Promise} + */ +const routes = async (fastify, options) => { + fastify.get("/example/get/:name", async (req, resp) => { + return {msg: `Hello, ${req.params.name}!`}; + }); + + fastify.post("/example/post", async (req, resp) => { + return {msg: `Goodbye, ${req.body.name}!`}; + }); +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/routes/query.js b/components/log-viewer-webui/server/src/routes/query.js new file mode 100644 index 000000000..452f6480a --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/query.js @@ -0,0 +1,63 @@ +import {StatusCodes} from "http-status-codes"; + +import settings from "../../settings.json" with {type: "json"}; +import {EXTRACT_JOB_TYPES} from "../DbManager.js"; + + +/** + * Creates query routes. + * + * @param {import("fastify").FastifyInstance | {dbManager: DbManager}} fastify + * @param {import("fastify").FastifyPluginOptions} options + * @return {Promise} + */ +const routes = async (fastify, options) => { + fastify.post("/query/extract-stream", async (req, resp) => { + const {extractJobType, logEventIdx, streamId} = req.body; + if (false === EXTRACT_JOB_TYPES.includes(extractJobType)) { + resp.code(StatusCodes.BAD_REQUEST); + throw new Error(`Invalid extractJobType="${extractJobType}".`); + } + + if ("string" !== typeof streamId || 0 === streamId.trim().length) { + resp.code(StatusCodes.BAD_REQUEST); + throw new Error("\"streamId\" must be a non-empty string."); + } + + const sanitizedLogEventIdx = Number(logEventIdx); + let streamMetadata = await fastify.dbManager.getExtractedStreamFileMetadata( + streamId, + sanitizedLogEventIdx + ); + + if (null === streamMetadata) { + const extractResult = await fastify.dbManager.submitAndWaitForExtractStreamJob({ + jobType: extractJobType, + logEventIdx: sanitizedLogEventIdx, + streamId: streamId, + targetUncompressedSize: settings.StreamTargetUncompressedSize, + }); + + if (null === extractResult) { + resp.code(StatusCodes.BAD_REQUEST); + throw new Error("Unable to extract stream with " + + `streamId=${streamId} at logEventIdx=${sanitizedLogEventIdx}`); + } + + streamMetadata = await fastify.dbManager.getExtractedStreamFileMetadata( + streamId, + sanitizedLogEventIdx + ); + + if (null === streamMetadata) { + resp.code(StatusCodes.BAD_REQUEST); + throw new Error("Unable to find the metadata of extracted stream with " + + `streamId=${streamId} at logEventIdx=${sanitizedLogEventIdx}`); + } + } + + return streamMetadata; + }); +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/routes/static.js b/components/log-viewer-webui/server/src/routes/static.js new file mode 100644 index 000000000..6118c2855 --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/static.js @@ -0,0 +1,56 @@ +import path from "node:path"; +import process from "node:process"; +import {fileURLToPath} from "node:url"; + +import {fastifyStatic} from "@fastify/static"; + +import settings from "../../settings.json" with {type: "json"}; + + +/** + * Creates static files serving routes. + * + * @param {import("fastify").FastifyInstance} fastify + * @param {import("fastify").FastifyPluginOptions} options + */ +const routes = async (fastify, options) => { + const filename = fileURLToPath(import.meta.url); + const dirname = path.dirname(filename); + const rootDirname = path.resolve(dirname, "../.."); + + let streamFilesDir = settings.StreamFilesDir; + if (false === path.isAbsolute(streamFilesDir)) { + streamFilesDir = path.resolve(rootDirname, streamFilesDir); + } + await fastify.register(fastifyStatic, { + prefix: "/streams", + root: streamFilesDir, + }); + + let logViewerDir = settings.LogViewerDir; + if (false === path.isAbsolute(logViewerDir)) { + logViewerDir = path.resolve(rootDirname, logViewerDir); + } + await fastify.register(fastifyStatic, { + prefix: "/log-viewer", + root: logViewerDir, + decorateReply: false, + }); + + if ("production" === process.env.NODE_ENV) { + // In the development environment, we expect the client to use a separate webserver that + // supports live reloading. + let clientDir = settings.ClientDir; + if (false === path.isAbsolute(clientDir)) { + clientDir = path.resolve(rootDirname, settings.ClientDir); + } + + await fastify.register(fastifyStatic, { + prefix: "/", + root: clientDir, + decorateReply: false, + }); + } +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/utils.js b/components/log-viewer-webui/server/src/utils.js new file mode 100644 index 000000000..580ea76f8 --- /dev/null +++ b/components/log-viewer-webui/server/src/utils.js @@ -0,0 +1,13 @@ +const MILLIS_PER_SECOND = 1000; + +/** + * Creates a promise that resolves after a specified number of seconds. + * + * @param {number} seconds Number of seconds to wait before resolving the promise. + * @return {Promise} A promise that resolves after the specified delay. + */ +const sleep = (seconds) => new Promise((resolve) => { + setTimeout(resolve, seconds * MILLIS_PER_SECOND); +}); + +export {sleep}; diff --git a/components/log-viewer-webui/yscope-log-viewer b/components/log-viewer-webui/yscope-log-viewer new file mode 160000 index 000000000..969ff35b2 --- /dev/null +++ b/components/log-viewer-webui/yscope-log-viewer @@ -0,0 +1 @@ +Subproject commit 969ff35b2387bcdc3580b441907e3656640ce16d diff --git a/components/package-template/src/etc/clp-config.yml b/components/package-template/src/etc/clp-config.yml index a9ffcc924..f19b93463 100644 --- a/components/package-template/src/etc/clp-config.yml +++ b/components/package-template/src/etc/clp-config.yml @@ -20,7 +20,7 @@ # jobs_poll_delay: 0.1 # seconds # logging_level: "INFO" # -#search_scheduler: +#query_scheduler: # host: "localhost" # port: 7000 # jobs_poll_delay: 0.1 # seconds @@ -34,7 +34,7 @@ #redis: # host: "localhost" # port: 6379 -# search_backend_database: 0 +# query_backend_database: 0 # compression_backend_database: 1 # #reducer: @@ -46,12 +46,13 @@ #results_cache: # host: "localhost" # port: 27017 -# db_name: "clp-search" +# db_name: "clp-query-results" +# stream_collection_name: "stream-files" # #compression_worker: # logging_level: "INFO" # -#search_worker: +#query_worker: # logging_level: "INFO" # #webui: @@ -59,6 +60,10 @@ # port: 4000 # logging_level: "INFO" # +#log_viewer_webui: +# host: "localhost" +# port: 3000 +# ## Where archives should be output to #archive_output: # directory: "var/data/archives" @@ -77,6 +82,13 @@ # # How much data CLP should try to fit into each segment within an archive # target_segment_size: 268435456 # 256 MB # +## Where CLP stream files (e.g., IR streams) should be output +#stream_output: +# directory: "var/data/streams" +# +# # How large each stream file should be before being split into a new stream file +# target_uncompressed_size: 134217728 # 128 MB +# ## Location where other data (besides archives) are stored. It will be created if ## it doesn't exist. #data_directory: "var/data" diff --git a/components/package-template/src/sbin/admin-tools/del-archives.sh b/components/package-template/src/sbin/admin-tools/del-archives.sh new file mode 100755 index 000000000..4d7ebc6b7 --- /dev/null +++ b/components/package-template/src/sbin/admin-tools/del-archives.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +package_root="$script_dir/../.." + +PYTHONPATH=$(readlink -f "$package_root/lib/python3/site-packages") \ + python3 \ + -m clp_package_utils.scripts.del_archives \ + "$@" diff --git a/components/webui/.meteor/packages b/components/webui/.meteor/packages index 367584235..88d4b182a 100644 --- a/components/webui/.meteor/packages +++ b/components/webui/.meteor/packages @@ -4,20 +4,16 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base@1.5.1 # Packages every Meteor app needs to have -mobile-experience@1.1.1 # Packages for a great mobile UX -mongo@1.16.8 # The database Meteor supports right now -reactive-var@1.0.12 # Reactive variable for tracker +meteor-base@1.5.1 # Packages every Meteor app needs to have +mongo@1.16.10 # The database Meteor supports right now +reactive-var@1.0.12 # Reactive variable for tracker -standard-minifier-css@1.9.2 # CSS minifier run for production mode -standard-minifier-js@2.8.1 # JS minifier run for production mode -es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers -ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code -typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules -shell-server@0.5.0 # Server-side component of the `meteor shell` command -hot-module-replacement@0.5.3 # Update client in development without reloading the page +standard-minifier-css@1.9.2 # CSS minifier run for production mode +standard-minifier-js@2.8.1 # JS minifier run for production mode +ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code +hot-module-replacement@0.5.3 # Update client in development without reloading the page -static-html@1.3.2 # Define static page content in .html files -react-meteor-data # React higher-order component for reactively tracking Meteor data -fourseven:scss -meteortesting:mocha +static-html@1.3.2 # Define static page content in .html files +react-meteor-data@2.7.2 # React higher-order component for reactively tracking Meteor data +fourseven:scss@4.16.0 # Compile Sass files with node-sass +meteortesting:mocha@2.1.0 # Testing framework diff --git a/components/webui/.meteor/release b/components/webui/.meteor/release index 966586ce5..5152abe9d 100644 --- a/components/webui/.meteor/release +++ b/components/webui/.meteor/release @@ -1 +1 @@ -METEOR@2.15 +METEOR@2.16 diff --git a/components/webui/.meteor/versions b/components/webui/.meteor/versions index 036863b05..d568e975c 100644 --- a/components/webui/.meteor/versions +++ b/components/webui/.meteor/versions @@ -4,16 +4,16 @@ babel-compiler@7.10.5 babel-runtime@1.5.1 base64@1.0.12 binary-heap@1.0.11 -blaze-tools@1.1.4 +blaze-tools@1.1.3 boilerplate-generator@1.7.2 caching-compiler@1.2.2 -caching-html-compiler@1.2.2 +caching-html-compiler@1.2.1 callback-hook@1.5.1 -check@1.3.2 +check@1.4.1 ddp@1.4.1 -ddp-client@2.6.1 -ddp-common@1.4.0 -ddp-server@2.7.0 +ddp-client@2.6.2 +ddp-common@1.4.1 +ddp-server@2.7.1 diff-sequence@1.1.2 dynamic-import@0.7.3 ecmascript@0.16.8 @@ -27,28 +27,25 @@ fourseven:scss@4.16.0 geojson-utils@1.0.11 hot-code-push@1.0.4 hot-module-replacement@0.5.3 -html-tools@1.1.4 -htmljs@1.2.1 +html-tools@1.1.3 +htmljs@1.1.1 http@1.0.10 id-map@1.1.1 inter-process-messaging@0.1.1 -launch-screen@2.0.0 -logging@1.3.3 +logging@1.3.4 meteor@1.11.5 meteor-base@1.5.1 meteortesting:browser-tests@1.4.2 meteortesting:mocha@2.1.0 meteortesting:mocha-core@8.0.1 minifier-css@1.6.4 -minifier-js@2.7.5 -minimongo@1.9.3 -mobile-experience@1.1.1 -mobile-status-bar@1.1.0 +minifier-js@2.8.0 +minimongo@1.9.4 modern-browsers@0.1.10 modules@0.20.0 modules-runtime@0.13.1 modules-runtime-hot@0.14.2 -mongo@1.16.9 +mongo@1.16.10 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 @@ -62,16 +59,15 @@ reactive-var@1.0.12 reload@1.3.1 retry@1.1.0 routepolicy@1.1.1 -shell-server@0.5.0 socket-stream-client@0.5.2 -spacebars-compiler@1.3.2 +spacebars-compiler@1.3.1 standard-minifier-css@1.9.2 standard-minifier-js@2.8.1 static-html@1.3.2 -templating-tools@1.2.3 +templating-tools@1.2.2 tracker@1.3.3 typescript@4.9.5 -underscore@1.6.1 +underscore@1.6.2 url@1.3.2 webapp@1.13.8 webapp-hashing@1.1.1 diff --git a/components/webui/.meteorignore b/components/webui/.meteorignore new file mode 100644 index 000000000..48357e9f1 --- /dev/null +++ b/components/webui/.meteorignore @@ -0,0 +1 @@ +linter diff --git a/components/webui/imports/api/ingestion/server/publications.js b/components/webui/imports/api/ingestion/server/publications.js index 6a558e7fe..61aefb8a1 100644 --- a/components/webui/imports/api/ingestion/server/publications.js +++ b/components/webui/imports/api/ingestion/server/publications.js @@ -4,10 +4,13 @@ import {logger} from "/imports/utils/logger"; import {MONGO_SORT_BY_ID} from "/imports/utils/mongo"; import { - CompressionJobsCollection, STATS_COLLECTION_ID, StatsCollection, + CompressionJobsCollection, + STATS_COLLECTION_ID, + StatsCollection, } from "../collections"; import { - COMPRESSION_JOB_WAITING_STATES, COMPRESSION_JOBS_TABLE_COLUMN_NAMES, + COMPRESSION_JOB_WAITING_STATES, + COMPRESSION_JOBS_TABLE_COLUMN_NAMES, } from "../constants"; import CompressionDbManager from "./CompressionDbManager"; import StatsDbManager from "./StatsDbManager"; diff --git a/components/webui/imports/api/search/constants.js b/components/webui/imports/api/search/constants.js index 0d0be9e94..baedddb85 100644 --- a/components/webui/imports/api/search/constants.js +++ b/components/webui/imports/api/search/constants.js @@ -58,29 +58,43 @@ const isOperationInProgress = (s) => ( ); /* eslint-disable sort-keys */ -let enumSearchJobStatus; +let enumQueryJobStatus; /** - * Enum of job statuses, matching the `SearchJobStatus` class in - * `job_orchestration.search_scheduler.constants`. + * Enum of job statuses, matching the `QueryJobStatus` class in + * `job_orchestration.query_scheduler.constants`. * * @enum {number} */ -const SEARCH_JOB_STATUS = Object.freeze({ - PENDING: (enumSearchJobStatus = 0), - RUNNING: ++enumSearchJobStatus, - SUCCEEDED: ++enumSearchJobStatus, - FAILED: ++enumSearchJobStatus, - CANCELLING: ++enumSearchJobStatus, - CANCELLED: ++enumSearchJobStatus, +const QUERY_JOB_STATUS = Object.freeze({ + PENDING: (enumQueryJobStatus = 0), + RUNNING: ++enumQueryJobStatus, + SUCCEEDED: ++enumQueryJobStatus, + FAILED: ++enumQueryJobStatus, + CANCELLING: ++enumQueryJobStatus, + CANCELLED: ++enumQueryJobStatus, }); /* eslint-enable sort-keys */ -const SEARCH_JOB_STATUS_WAITING_STATES = [ - SEARCH_JOB_STATUS.PENDING, - SEARCH_JOB_STATUS.RUNNING, - SEARCH_JOB_STATUS.CANCELLING, +const QUERY_JOB_STATUS_WAITING_STATES = [ + QUERY_JOB_STATUS.PENDING, + QUERY_JOB_STATUS.RUNNING, + QUERY_JOB_STATUS.CANCELLING, ]; +/* eslint-disable sort-keys */ +let enumQueryType; +/** + * Enum of job type, matching the `QueryJobType` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_TYPE = Object.freeze({ + SEARCH_OR_AGGREGATION: (enumQueryType = 0), + EXTRACT_IR: ++enumQueryType, +}); +/* eslint-enable sort-keys */ + /** * Enum of Mongo Collection sort orders. * @@ -112,8 +126,9 @@ export { isSearchSignalReq, isSearchSignalResp, MONGO_SORT_ORDER, - SEARCH_JOB_STATUS, - SEARCH_JOB_STATUS_WAITING_STATES, + QUERY_JOB_STATUS, + QUERY_JOB_STATUS_WAITING_STATES, + QUERY_JOB_TYPE, SEARCH_MAX_NUM_RESULTS, SEARCH_RESULTS_FIELDS, SEARCH_SIGNAL, diff --git a/components/webui/imports/api/search/server/SearchJobsDbManager.js b/components/webui/imports/api/search/server/QueryJobsDbManager.js similarity index 62% rename from components/webui/imports/api/search/server/SearchJobsDbManager.js rename to components/webui/imports/api/search/server/QueryJobsDbManager.js index df1dc27fa..9efc4771c 100644 --- a/components/webui/imports/api/search/server/SearchJobsDbManager.js +++ b/components/webui/imports/api/search/server/QueryJobsDbManager.js @@ -1,10 +1,11 @@ -import msgpack from "@msgpack/msgpack"; +import {encode} from "@msgpack/msgpack"; import {sleep} from "/imports/utils/misc"; import { - SEARCH_JOB_STATUS, - SEARCH_JOB_STATUS_WAITING_STATES, + QUERY_JOB_STATUS, + QUERY_JOB_STATUS_WAITING_STATES, + QUERY_JOB_TYPE, } from "../constants"; @@ -14,32 +15,33 @@ import { const JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS = 0.5; /** - * Enum of the `search_jobs` table's column names. + * Enum of the `query_jobs` table's column names. * * @enum {string} */ -const SEARCH_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ +const QUERY_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ ID: "id", STATUS: "status", - SEARCH_CONFIG: "search_config", + TYPE: "type", + JOB_CONFIG: "job_config", }); /** - * Class for submitting and monitoring search jobs in the database. + * Class for submitting and monitoring query jobs in the database. */ -class SearchJobsDbManager { +class QueryJobsDbManager { #sqlDbConnPool; - #searchJobsTableName; + #queryJobsTableName; /** * @param {import("mysql2/promise").Pool} sqlDbConnPool * @param {object} tableNames - * @param {string} tableNames.searchJobsTableName + * @param {string} tableNames.queryJobsTableName */ - constructor (sqlDbConnPool, {searchJobsTableName}) { + constructor (sqlDbConnPool, {queryJobsTableName}) { this.#sqlDbConnPool = sqlDbConnPool; - this.#searchJobsTableName = searchJobsTableName; + this.#queryJobsTableName = queryJobsTableName; } /** @@ -51,10 +53,12 @@ class SearchJobsDbManager { */ async submitSearchJob (searchConfig) { const [queryInsertResults] = await this.#sqlDbConnPool.query( - `INSERT INTO ${this.#searchJobsTableName} - (${SEARCH_JOBS_TABLE_COLUMN_NAMES.SEARCH_CONFIG}) - VALUES (?)`, - [Buffer.from(msgpack.encode(searchConfig))], + `INSERT INTO ${this.#queryJobsTableName} + (${QUERY_JOBS_TABLE_COLUMN_NAMES.JOB_CONFIG}, + ${QUERY_JOBS_TABLE_COLUMN_NAMES.TYPE}) + VALUES (?, ?)`, + [Buffer.from(encode(searchConfig)), + QUERY_JOB_TYPE.SEARCH_OR_AGGREGATION], ); return queryInsertResults.insertId; @@ -88,11 +92,11 @@ class SearchJobsDbManager { */ async submitQueryCancellation (jobId) { await this.#sqlDbConnPool.query( - `UPDATE ${this.#searchJobsTableName} - SET ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} = ${SEARCH_JOB_STATUS.CANCELLING} - WHERE ${SEARCH_JOBS_TABLE_COLUMN_NAMES.ID} = ? - AND ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} - IN (${SEARCH_JOB_STATUS.PENDING}, ${SEARCH_JOB_STATUS.RUNNING})`, + `UPDATE ${this.#queryJobsTableName} + SET ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} = ${QUERY_JOB_STATUS.CANCELLING} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? + AND ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + IN (${QUERY_JOB_STATUS.PENDING}, ${QUERY_JOB_STATUS.RUNNING})`, jobId, ); } @@ -111,9 +115,9 @@ class SearchJobsDbManager { try { const [queryRows] = await this.#sqlDbConnPool.query( ` - SELECT ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} - FROM ${this.#searchJobsTableName} - WHERE ${SEARCH_JOBS_TABLE_COLUMN_NAMES.ID} = ? + SELECT ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + FROM ${this.#queryJobsTableName} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? `, jobId, ); @@ -125,14 +129,14 @@ class SearchJobsDbManager { if (0 === rows.length) { throw new Error(`Job ${jobId} not found in database.`); } - const status = rows[0][SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS]; + const status = rows[0][QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS]; - if (false === SEARCH_JOB_STATUS_WAITING_STATES.includes(status)) { - if (SEARCH_JOB_STATUS.CANCELLED === status) { + if (false === QUERY_JOB_STATUS_WAITING_STATES.includes(status)) { + if (QUERY_JOB_STATUS.CANCELLED === status) { throw new Error(`Job ${jobId} was cancelled.`); - } else if (SEARCH_JOB_STATUS.SUCCEEDED !== status) { + } else if (QUERY_JOB_STATUS.SUCCEEDED !== status) { throw new Error(`Job ${jobId} exited with unexpected status=${status}: ` + - `${Object.keys(SEARCH_JOB_STATUS)[status]}.`); + `${Object.keys(QUERY_JOB_STATUS)[status]}.`); } break; } @@ -142,4 +146,4 @@ class SearchJobsDbManager { } } -export default SearchJobsDbManager; +export default QueryJobsDbManager; diff --git a/components/webui/imports/api/search/server/methods.js b/components/webui/imports/api/search/server/methods.js index b0b6a5851..695b6b4a6 100644 --- a/components/webui/imports/api/search/server/methods.js +++ b/components/webui/imports/api/search/server/methods.js @@ -9,24 +9,24 @@ import { } from "../constants"; import {ERROR_NAME_COLLECTION_DROPPED} from "../SearchJobCollectionsManager"; import {searchJobCollectionsManager} from "./collections"; -import SearchJobsDbManager from "./SearchJobsDbManager"; +import QueryJobsDbManager from "./QueryJobsDbManager"; /** - * @type {SearchJobsDbManager|null} + * @type {QueryJobsDbManager|null} */ -let searchJobsDbManager = null; +let queryJobsDbManager = null; /** - * Initializes the SearchJobsDbManager. + * Initializes the QueryJobsDbManager. * * @param {import("mysql2/promise").Pool} sqlDbConnPool * @param {object} tableNames - * @param {string} tableNames.searchJobsTableName + * @param {string} tableNames.queryJobsTableName * @throws {Error} on error. */ -const initSearchJobsDbManager = (sqlDbConnPool, {searchJobsTableName}) => { - searchJobsDbManager = new SearchJobsDbManager(sqlDbConnPool, {searchJobsTableName}); +const initQueryJobsDbManager = (sqlDbConnPool, {queryJobsTableName}) => { + queryJobsDbManager = new QueryJobsDbManager(sqlDbConnPool, {queryJobsTableName}); }; /** @@ -67,8 +67,8 @@ const updateSearchSignalWhenJobsFinish = async ({ }) => { let errorMsg; try { - await searchJobsDbManager.awaitJobCompletion(searchJobId); - await searchJobsDbManager.awaitJobCompletion(aggregationJobId); + await queryJobsDbManager.awaitJobCompletion(searchJobId); + await queryJobsDbManager.awaitJobCompletion(aggregationJobId); } catch (e) { errorMsg = e.message; } @@ -169,9 +169,9 @@ Meteor.methods({ let searchJobId; let aggregationJobId; try { - searchJobId = await searchJobsDbManager.submitSearchJob(args); + searchJobId = await queryJobsDbManager.submitSearchJob(args); aggregationJobId = - await searchJobsDbManager.submitAggregationJob(args, timeRangeBucketSizeMillis); + await queryJobsDbManager.submitAggregationJob(args, timeRangeBucketSizeMillis); } catch (e) { const errorMsg = "Unable to submit search/aggregation job to the SQL database."; logger.error(errorMsg, e.toString()); @@ -241,8 +241,8 @@ Meteor.methods({ `aggregationJobId=${aggregationJobId}`); try { - await searchJobsDbManager.submitQueryCancellation(searchJobId); - await searchJobsDbManager.submitQueryCancellation(aggregationJobId); + await queryJobsDbManager.submitQueryCancellation(searchJobId); + await queryJobsDbManager.submitQueryCancellation(aggregationJobId); updateSearchResultsMeta({ jobId: searchJobId, lastSignal: SEARCH_SIGNAL.RESP_QUERYING, @@ -260,4 +260,4 @@ Meteor.methods({ }, }); -export {initSearchJobsDbManager}; +export {initQueryJobsDbManager}; diff --git a/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx b/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx index 7f763ac19..382212ad6 100644 --- a/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx +++ b/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx @@ -8,7 +8,9 @@ import Row from "react-bootstrap/Row"; import { computeTimeRange, convertLocalDateToSameUtcDatetime, - convertUtcDatetimeToSameLocalDate, TIME_RANGE_PRESET_LABEL, TIME_UNIT, + convertUtcDatetimeToSameLocalDate, + TIME_RANGE_PRESET_LABEL, + TIME_UNIT, } from "/imports/utils/datetime"; import SearchControlsFilterLabel from "../SearchControlsFilterLabel"; diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx index 9c9c0e97c..b7f62e1d8 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx @@ -1,5 +1,6 @@ import { - useEffect, useRef, + useEffect, + useRef, } from "react"; import Spinner from "react-bootstrap/Spinner"; diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss index 6c24ec2da..063900dfb 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss @@ -27,6 +27,8 @@ } .search-results-content { + overflow: auto; + font-size: 0.875rem; line-height: var(--search-results-message-line-height); } @@ -48,3 +50,12 @@ word-break: break-word; } +.search-results-file-link { + margin-top: 0.25rem; + + color: grey; + font-family: 'Roboto', sans-serif; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.5; +} diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx index bbdf11ca8..c7fa62a25 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx @@ -1,15 +1,20 @@ +import {Meteor} from "meteor/meteor"; import {useEffect} from "react"; import Table from "react-bootstrap/Table"; import dayjs from "dayjs"; import { - faSort, faSortDown, faSortUp, + faFileLines, + faSort, + faSortDown, + faSortUp, } from "@fortawesome/free-solid-svg-icons"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { - MONGO_SORT_ORDER, SEARCH_RESULTS_FIELDS, + MONGO_SORT_ORDER, + SEARCH_RESULTS_FIELDS, } from "/imports/api/search/constants"; import {DATETIME_FORMAT_TEMPLATE} from "/imports/utils/datetime"; @@ -23,6 +28,25 @@ import "./SearchResultsTable.scss"; */ const SEARCH_RESULT_MESSAGE_LINE_HEIGHT = 1.5; +const IS_IR_STREAM = ("clp" === Meteor.settings.public.ClpStorageEngine); +const STREAM_TYPE = IS_IR_STREAM ? + "ir" : + "json"; + + +/** + * Gets the stream id for an extraction job from the search result. + * + * @param {object} searchResult + * @return {string} stream_id + */ +const getStreamId = (searchResult) => { + return IS_IR_STREAM ? + searchResult.orig_file_id : + searchResult.archive_id; +}; + + /** * Represents a table component to display search results. * @@ -32,7 +56,7 @@ const SEARCH_RESULT_MESSAGE_LINE_HEIGHT = 1.5; * @param {boolean} props.hasMoreResultsInTotal * @param {number} props.maxLinesPerResult * @param {Function} props.onLoadMoreResults - * @param {object} props.searchResults + * @param {object[]} props.searchResults * @param {Function} props.setFieldToSortBy * @return {React.ReactElement} */ @@ -88,25 +112,6 @@ const SearchResultsTable = ({ ); }, [maxLinesPerResult]); - const rows = []; - for (let i = 0; i < searchResults.length; ++i) { - const searchResult = searchResults[i]; - rows.push( - - - {searchResult.timestamp ? - dayjs.utc(searchResult.timestamp).format(DATETIME_FORMAT_TEMPLATE) : - "N/A"} - - -
-                        {searchResult.message}
-                    
- - - ); - } - return (
@@ -131,7 +135,6 @@ const SearchResultsTable = ({
- {rows} + {searchResults.map((result) => ( + + + + + ))}
Log message @@ -140,7 +143,38 @@ const SearchResultsTable = ({
+ {result.timestamp ? + dayjs.utc(result.timestamp).format(DATETIME_FORMAT_TEMPLATE) : + "N/A"} + +
+                                    {result.message}
+                                
+ +
} * @throws {Error} on error. */ @@ -46,7 +46,7 @@ const initDbManagers = async ({ clpArchivesTableName, clpFilesTableName, compressionJobsTableName, - searchJobsTableName, + queryJobsTableName, }) => { if (null !== dbConnPool) { throw Error("This method should not be called twice."); @@ -76,8 +76,8 @@ const initDbManagers = async ({ initCompressionDbManager(dbConnPool, { compressionJobsTableName, }); - initSearchJobsDbManager(dbConnPool, { - searchJobsTableName, + initQueryJobsDbManager(dbConnPool, { + queryJobsTableName, }); initStatsDbManager(dbConnPool, { clpArchivesTableName, diff --git a/components/webui/package-lock.json b/components/webui/package-lock.json index 700574212..c7644cc78 100644 --- a/components/webui/package-lock.json +++ b/components/webui/package-lock.json @@ -13,34 +13,34 @@ } }, "@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "requires": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==" + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==" }, "@babel/core": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", - "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.4", - "@babel/parser": "^7.24.4", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -49,202 +49,172 @@ } }, "@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "requires": { - "@babel/types": "^7.24.0", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" } }, "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.25.7" } }, "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, "@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "requires": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" } }, "@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==" + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==" }, "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==" + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==" }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==" }, "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==" }, "@babel/helpers": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", - "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", "requires": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "requires": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==" + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "requires": { + "@babel/types": "^7.25.8" + } }, "@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", + "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.7" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.7.tgz", + "integrity": "sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-jsx": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "requires": { "regenerator-runtime": "^0.14.0" } }, "@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" } }, "@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "requires": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@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.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "requires": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" } }, @@ -264,30 +234,30 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", - "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==" }, "@fortawesome/fontawesome-svg-core": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", - "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.2" + "@fortawesome/fontawesome-common-types": "6.6.0" } }, "@fortawesome/free-solid-svg-icons": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", - "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", "requires": { - "@fortawesome/fontawesome-common-types": "6.5.2" + "@fortawesome/fontawesome-common-types": "6.6.0" } }, "@fortawesome/react-fontawesome": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", - "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", "requires": { "prop-types": "^15.8.1" } @@ -313,9 +283,9 @@ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "@jridgewell/trace-mapping": { "version": "0.3.25", @@ -351,9 +321,9 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@react-aria/ssr": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", - "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.6.tgz", + "integrity": "sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==", "requires": { "@swc/helpers": "^0.5.0" } @@ -367,9 +337,9 @@ } }, "@restart/ui": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz", - "integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.8.0.tgz", + "integrity": "sha512-xJEOXUOTmT4FngTmhdjKFRrVVF0hwCLNPdatLCHkyS4dkiSK12cEu1Y0fjxktjJrdst9jJIc5J6ihMJCoWEN/g==", "requires": { "@babel/runtime": "^7.21.0", "@popperjs/core": "^2.11.6", @@ -390,53 +360,53 @@ } }, "@swc/helpers": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", - "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", "requires": { "tslib": "^2.4.0" } }, "@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true }, "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, "@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "@types/react": { - "version": "18.2.79", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", - "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", "requires": { "@types/react": "*" } @@ -451,6 +421,14 @@ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -466,9 +444,19 @@ "dev": true }, "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bluebird": { "version": "3.7.2", @@ -482,20 +470,29 @@ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==" }, "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "requires": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "caniuse-lite": { - "version": "1.0.30001612", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", - "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==" + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==" }, "catharsis": { "version": "0.9.0", @@ -517,9 +514,9 @@ } }, "chart.js": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", - "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz", + "integrity": "sha512-CVVjg1RYTJV9OCC8WeJPMx8gsV8K6WIyIEQUE3ui4AR9Hfgls9URri6Ja3hyMVBbTF8Q2KFa19PE815gWcWhng==", "requires": { "@kurkle/color": "^0.3.0" } @@ -601,16 +598,16 @@ } }, "dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "denque": { @@ -633,9 +630,9 @@ } }, "electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==" + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==" }, "enabled": { "version": "2.0.0", @@ -643,21 +640,31 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -739,6 +746,11 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -787,21 +799,21 @@ } }, "jsdoc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", - "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", "dev": true, "requires": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", @@ -818,9 +830,9 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" }, "json5": { "version": "2.2.3", @@ -842,12 +854,12 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "lodash": { @@ -857,9 +869,9 @@ "dev": true }, "logform": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", - "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", + "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", "requires": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", @@ -890,17 +902,23 @@ "yallist": "^3.0.2" } }, + "lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==" + }, "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "requires": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" } }, "markdown-it-anchor": { @@ -916,15 +934,15 @@ "dev": true }, "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "meteor-node-stubs": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.9.tgz", - "integrity": "sha512-EKRezc1/PblYtYiK4BOT3h5geWDo9AFBBSYNamPNh8AC5msUbVCcg8kekzAa7r7JPzBX8nZWaXEQVar4t8q/Hg==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.10.tgz", + "integrity": "sha512-zP1AVg8sOATz15yfy11R2VTx+IZFfAOXi8GuZa8tOfeVT1tKaqDooAbFylnIXwpStSu6HRBUhQqHtR06Qr9aEA==", "requires": { "@meteorjs/crypto-browserify": "^3.12.1", "assert": "^2.1.0", @@ -933,7 +951,7 @@ "console-browserify": "^1.2.0", "constants-browserify": "^1.0.0", "domain-browser": "^4.23.0", - "elliptic": "^6.5.4", + "elliptic": "^6.5.7", "events": "^3.3.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", @@ -947,7 +965,7 @@ "string_decoder": "^1.3.0", "timers-browserify": "^2.0.12", "tty-browserify": "0.0.1", - "url": "^0.11.3", + "url": "^0.11.4", "util": "^0.12.5", "vm-browserify": "^1.1.2" }, @@ -1147,12 +1165,14 @@ "bundled": true }, "call-bind": { - "version": "1.0.5", + "version": "1.0.7", "bundled": true, "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "cipher-base": { @@ -1213,12 +1233,12 @@ } }, "define-data-property": { - "version": "1.1.1", + "version": "1.1.4", "bundled": true, "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-properties": { @@ -1258,7 +1278,7 @@ "bundled": true }, "elliptic": { - "version": "6.5.5", + "version": "6.5.7", "bundled": true, "requires": { "bn.js": "^4.11.9", @@ -1276,6 +1296,17 @@ } } }, + "es-define-property": { + "version": "1.0.0", + "bundled": true, + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "bundled": true + }, "events": { "version": "3.3.0", "bundled": true @@ -1300,9 +1331,10 @@ "bundled": true }, "get-intrinsic": { - "version": "1.2.2", + "version": "1.2.4", "bundled": true, "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -1317,10 +1349,10 @@ } }, "has-property-descriptors": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "requires": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" } }, "has-proto": { @@ -1453,7 +1485,7 @@ "bundled": true }, "object-inspect": { - "version": "1.13.1", + "version": "1.13.2", "bundled": true }, "object-is": { @@ -1554,10 +1586,10 @@ "bundled": true }, "qs": { - "version": "6.11.2", + "version": "6.13.0", "bundled": true, "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "querystring-es3": { @@ -1601,13 +1633,15 @@ "bundled": true }, "set-function-length": { - "version": "1.1.1", + "version": "1.2.2", "bundled": true, "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "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", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" } }, "setimmediate": { @@ -1623,12 +1657,13 @@ } }, "side-channel": { - "version": "1.0.4", + "version": "1.0.6", "bundled": true, "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "stream-browserify": { @@ -1668,11 +1703,11 @@ "bundled": true }, "url": { - "version": "0.11.3", + "version": "0.11.4", "bundled": true, "requires": { "punycode": "^1.4.1", - "qs": "^6.11.2" + "qs": "^6.12.3" } }, "util": { @@ -1723,30 +1758,24 @@ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mysql2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.0.tgz", - "integrity": "sha512-qx0mfWYt1DpTPkw8mAcHW/OwqqyNqBLBHvY5IjN8+icIYTjt6znrgYJ+gxqNNRpVknb5Wc/gcCM4XjbCR0j5tw==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.3.tgz", + "integrity": "sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ==", "requires": { + "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^5.2.1", - "lru-cache": "^8.0.0", + "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" - }, - "dependencies": { - "lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" - } } }, "named-placeholders": { @@ -1765,9 +1794,9 @@ } }, "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "object-assign": { "version": "4.1.1", @@ -1788,17 +1817,22 @@ } }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "requires": { "isarray": "0.0.1" } }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, "prop-types": { "version": "15.8.1", @@ -1819,22 +1853,28 @@ "warning": "^4.0.0" } }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true + }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { "loose-envify": "^1.1.0" } }, "react-bootstrap": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz", - "integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==", + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", + "integrity": "sha512-XueAOEn64RRkZ0s6yzUTdpFtdUXs5L5491QU//8ZcODKJNDLt/r01tNyriZccjgRImH1REynUc9pqjiRMpDLWQ==", "requires": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.24.7", "@restart/hooks": "^0.4.9", - "@restart/ui": "^1.6.8", + "@restart/ui": "^1.6.9", "@types/react-transition-group": "^4.4.6", "classnames": "^2.3.2", "dom-helpers": "^5.2.1", @@ -1865,12 +1905,12 @@ } }, "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" } }, "react-fast-compare": { @@ -1889,9 +1929,9 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-onclickoutside": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", - "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==" + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz", + "integrity": "sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==" }, "react-popper": { "version": "2.3.0", @@ -1978,9 +2018,9 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" }, "safer-buffer": { "version": "2.1.2", @@ -1988,9 +2028,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { "loose-envify": "^1.1.0" } @@ -2071,14 +2111,14 @@ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" }, "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "uncontrollable": { @@ -2093,18 +2133,18 @@ } }, "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "dev": true }, "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" } }, "util-deprecate": { @@ -2131,15 +2171,15 @@ } }, "winston": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", - "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", + "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", "requires": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.6.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", @@ -2160,13 +2200,27 @@ } }, "winston-transport": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", - "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.8.0.tgz", + "integrity": "sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==", "requires": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", + "logform": "^2.6.1", + "readable-stream": "^4.5.2", "triple-beam": "^1.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } } }, "xmlcreate": { diff --git a/components/webui/package.json b/components/webui/package.json index 5b34be3f9..8ba99fc11 100644 --- a/components/webui/package.json +++ b/components/webui/package.json @@ -25,7 +25,7 @@ "chartjs-plugin-zoom": "^2.0.1", "dayjs": "^1.11.10", "json5": "^2.2.3", - "meteor-node-stubs": "^1.2.9", + "meteor-node-stubs": "^1.2.10", "mysql2": "^3.10.0", "react": "^18.2.0", "react-bootstrap": "^2.10.2", diff --git a/components/webui/server/main.js b/components/webui/server/main.js index 93992d1d3..2660e16c1 100644 --- a/components/webui/server/main.js +++ b/components/webui/server/main.js @@ -67,7 +67,7 @@ Meteor.startup(async () => { clpArchivesTableName: Meteor.settings.private.SqlDbClpArchivesTableName, clpFilesTableName: Meteor.settings.private.SqlDbClpFilesTableName, compressionJobsTableName: Meteor.settings.private.SqlDbCompressionJobsTableName, - searchJobsTableName: Meteor.settings.private.SqlDbSearchJobsTableName, + queryJobsTableName: Meteor.settings.private.SqlDbQueryJobsTableName, }); }); diff --git a/components/webui/settings.json b/components/webui/settings.json index 7cf574b32..f959dac8e 100644 --- a/components/webui/settings.json +++ b/components/webui/settings.json @@ -7,12 +7,13 @@ "SqlDbClpArchivesTableName": "clp_archives", "SqlDbClpFilesTableName": "clp_files", "SqlDbCompressionJobsTableName": "compression_jobs", - "SqlDbSearchJobsTableName": "search_jobs" + "SqlDbQueryJobsTableName": "query_jobs" }, "public": { "AggregationResultsCollectionName": "aggregation-results", "ClpStorageEngine": "clp", "CompressionJobsCollectionName": "compression-jobs", + "LogViewerWebuiUrl": "http://localhost:8080", "SearchResultsCollectionName": "search-results", "SearchResultsMetadataCollectionName": "results-metadata", "StatsCollectionName": "stats", diff --git a/deps-tasks.yml b/deps-tasks.yml new file mode 100644 index 000000000..3c60af001 --- /dev/null +++ b/deps-tasks.yml @@ -0,0 +1,445 @@ +version: "3" +vars: + # Utility script path + G_DEP_DOWNLOAD_SCRIPT: "{{.ROOT_DIR}}/tools/scripts/deps-download/download-dep.py" + + # Target checksum files + G_DEPS_CORE_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#core.md5" + G_DEPS_LOG_VIEWER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#log-viewer.md5" + + # Submodule checksum files + G_ABSEIL_CPP_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#abseil-cpp.md5" + G_ANTLR4_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#antlr4.md5" + G_CATCH2_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#Catch2.md5" + G_DATE_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#date.md5" + G_JSON_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#json.md5" + G_LOG_SURGEON_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#log-surgeon.md5" + G_OUTCOME_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#outcome.md5" + G_SIMDJSON_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#simdjson.md5" + G_SQLITE3_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#sqlite3.md5" + G_YAML_CPP_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#yaml-cpp.md5" + G_YSCOPE_LOG_VIEWER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/deps#yscope-log-viewer.md5" + +tasks: + default: + cmds: + - task: "core" + - task: "log-viewer" + + core: + vars: + sources: + - "{{.G_ABSEIL_CPP_CHECKSUM_FILE}}" + - "{{.G_ANTLR4_CHECKSUM_FILE}}" + - "{{.G_CATCH2_CHECKSUM_FILE}}" + - "{{.G_DATE_CHECKSUM_FILE}}" + - "{{.G_JSON_CHECKSUM_FILE}}" + - "{{.G_LOG_SURGEON_CHECKSUM_FILE}}" + - "{{.G_OUTCOME_CHECKSUM_FILE}}" + - "{{.G_SIMDJSON_CHECKSUM_FILE}}" + - "{{.G_SQLITE3_CHECKSUM_FILE}}" + - "{{.G_YAML_CPP_CHECKSUM_FILE}}" + generates: ["{{.G_DEPS_CORE_CHECKSUM_FILE}}"] + deps: ["all-internal-deps"] + cmds: + - >- + cat + "{{.G_ABSEIL_CPP_CHECKSUM_FILE}}" + "{{.G_ANTLR4_CHECKSUM_FILE}}" + "{{.G_CATCH2_CHECKSUM_FILE}}" + "{{.G_DATE_CHECKSUM_FILE}}" + "{{.G_JSON_CHECKSUM_FILE}}" + "{{.G_LOG_SURGEON_CHECKSUM_FILE}}" + "{{.G_OUTCOME_CHECKSUM_FILE}}" + "{{.G_SIMDJSON_CHECKSUM_FILE}}" + "{{.G_SQLITE3_CHECKSUM_FILE}}" + "{{.G_YAML_CPP_CHECKSUM_FILE}}" + >> "{{.G_DEPS_CORE_CHECKSUM_FILE}}" + + log-viewer: + sources: + - "{{.G_YSCOPE_LOG_VIEWER_CHECKSUM_FILE}}" + generates: ["{{.G_DEPS_LOG_VIEWER_CHECKSUM_FILE}}"] + deps: ["all-internal-deps"] + cmds: + - >- + cat + "{{.G_YSCOPE_LOG_VIEWER_CHECKSUM_FILE}}" + >> "{{.G_DEPS_LOG_VIEWER_CHECKSUM_FILE}}" + + # NOTE: `git submodule update` doesn't support parallel invocations, so we can't use it to + # download submodules in parallel. This means: + # 1. the submodule tasks cannot be specified as a list of `deps`, because `task` always runs + # `deps` tasks in parallel. + # 2. we need to force higher level tasks, like `core` and `log-viewer`, to run their submodule + # download tasks serially. + # + # As a solution, the `all-internal-deps` task below: + # 1. uses `cmds` to sequentially download `ALL` submodules required by the clp-package. + # 2. uses `run: once`, so it executes once even if multiple higher level targets depend on it. + # + # All higher level tasks must depend on this task instead of depending on the individual + # submodule tasks in this file. + all-internal-deps: + internal: true + run: "once" + cmds: + - task: "abseil-cpp" + - task: "antlr4" + - task: "Catch2" + - task: "date" + - task: "json" + - task: "log-surgeon" + - task: "outcome" + - task: "simdjson" + - task: "sqlite3" + - task: "yaml-cpp" + - task: "yscope-log-viewer" + + abseil-cpp: + internal: true + vars: + CHECKSUM_FILE: "{{.G_ABSEIL_CPP_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/abseil-cpp" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "abseil-cpp-20230802.1" + SRC_URL: "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.1.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + antlr4: + internal: true + vars: + CHECKSUM_FILE: "{{.G_ANTLR4_CHECKSUM_FILE}}" + DEST_DIR: "{{.G_CORE_COMPONENT_DIR}}/third-party/antlr" + DEST: "{{.DEST_DIR}}/antlr-4.13.1-complete.jar" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST_DIR}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--no-submodule" + SRC_NAME: "antlr-4.13.1-complete.jar" + SRC_URL: "https://www.antlr.org/download/antlr-4.13.1-complete.jar" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST_DIR}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + Catch2: + internal: true + vars: + CHECKSUM_FILE: "{{.G_CATCH2_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/Catch2" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "Catch2-2.13.7" + SRC_URL: "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.7.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + date: + internal: true + vars: + CHECKSUM_FILE: "{{.G_DATE_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/date" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "date-3.0.1" + SRC_URL: "https://github.com/HowardHinnant/date/archive/refs/tags/v3.0.1.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + json: + internal: true + vars: + CHECKSUM_FILE: "{{.G_JSON_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/json" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "json-3.11.3" + SRC_URL: "https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + log-surgeon: + internal: true + vars: + CHECKSUM_FILE: "{{.G_LOG_SURGEON_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/log-surgeon" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "log-surgeon-895f46489b1911ab3b3aac3202afd56c96e8cd98" + SRC_URL: "https://github.com/y-scope/log-surgeon/archive/895f464.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + outcome: + internal: true + vars: + CHECKSUM_FILE: "{{.G_OUTCOME_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/outcome" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "outcome-2.2.9" + SRC_URL: "https://github.com/ned14/outcome/archive/refs/tags/v2.2.9.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + simdjson: + internal: true + vars: + CHECKSUM_FILE: "{{.G_SIMDJSON_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/simdjson" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "simdjson-3.6.3" + SRC_URL: "https://github.com/simdjson/simdjson/archive/refs/tags/v3.6.3.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + # We don't use a git submodule for sqlite3 since that would require building the sqlite + # amalgamation + sqlite3: + internal: true + vars: + CHECKSUM_FILE: "{{.G_SQLITE3_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/sqlite3" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract --no-submodule" + SRC_NAME: "sqlite-amalgamation-3360000" + SRC_URL: "https://www.sqlite.org/2021/sqlite-amalgamation-3360000.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + yaml-cpp: + internal: true + vars: + CHECKSUM_FILE: "{{.G_YAML_CPP_CHECKSUM_FILE}}" + DEST: "{{.G_CORE_COMPONENT_SUBMODULES_DIR}}/yaml-cpp" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "yaml-cpp-yaml-cpp-0.7.0" + SRC_URL: "https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + yscope-log-viewer: + internal: true + vars: + CHECKSUM_FILE: "{{.G_YSCOPE_LOG_VIEWER_CHECKSUM_FILE}}" + DEST: "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/yscope-log-viewer" + sources: + - "{{.G_DEP_DOWNLOAD_SCRIPT}}" + - "{{.G_UTILS_TASKFILE}}" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - ":init" + - task: ":utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.DEST}}" + cmds: + - task: "download-dependency" + vars: + DEST: "{{.DEST}}" + FLAGS: "--extract" + SRC_NAME: "yscope-log-viewer-969ff35b2387bcdc3580b441907e3656640ce16d" + SRC_URL: "https://github.com/y-scope/yscope-log-viewer/archive/969ff35.zip" + # This command must be last + - task: ":utils:compute-checksum" + vars: + DATA_DIR: "{{.DEST}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + download-dependency: + internal: true + label: "{{.TASK}}-{{.SRC_NAME}}" + requires: + vars: ["DEST", "FLAGS", "SRC_NAME", "SRC_URL"] + deps: + - ":init" + cmds: + - >- + python3 "{{.G_DEP_DOWNLOAD_SCRIPT}}" + "{{.SRC_URL}}" + {{.SRC_NAME}} + "{{.DEST}}" + {{.FLAGS}} diff --git a/docs/README.md b/docs/README.md index 722bd625c..8f3d7207c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,7 @@ this project: the size of repo as we add and update images. * [Node.js] >= 16 to be able to [view the output](#viewing-the-output) * Python 3.10 or later -* [Task] >= 3.35 +* [Task] >= 3.38.0 * We constrain the version because, in lower versions, the Taskfile syntax we use has bugs. ## Build Commands @@ -44,3 +44,4 @@ the address it binds to (usually http://localhost:8080). [git-lfs]: https://git-lfs.com [http-server]: https://www.npmjs.com/package/http-server [Node.js]: https://nodejs.org/en/download/current +[Task]: https://taskfile.dev/ diff --git a/docs/requirements.txt b/docs/requirements.txt index ed28263a9..84466dcae 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,9 @@ myst-parser>=2.0.0 # Locked to avoid pydata/pydata-sphinx-theme#1676 until its fix is released in a version above # 0.15.2 pydata-sphinx-theme==0.14.4 -sphinx>=7.3.7 +# Locked to avoid the following issue until a fix is released: +# https://github.com/sphinx-doc/sphinx/issues/13002 +sphinx==8.0.2 sphinx_design>=0.5.0 sphinx-copybutton>=0.5.2 sphinxcontrib-mermaid>=0.9.2 diff --git a/docs/src/dev-guide/building-package.md b/docs/src/dev-guide/building-package.md index 32d0ae417..6d47185f4 100644 --- a/docs/src/dev-guide/building-package.md +++ b/docs/src/dev-guide/building-package.md @@ -11,21 +11,22 @@ prebuilt version instead, check out the [releases](https://github.com/y-scope/cl * It should be possible to build a package for a different environment, it just requires a some extra configuration. * Python 3.8 or newer +* python3-dev * python3-venv -* [Task](https://taskfile.dev/) +* [Task](https://taskfile.dev/) >= 3.38.0 ## Setup -Download CLP core's source dependencies: +Initialize the project ```shell -components/core/tools/scripts/deps-download/download-all.sh +tools/scripts/deps-download/init.sh ``` Install CLP core's dependencies ```shell -components/core/tools/ubuntu-focal/install-all.sh +components/core/tools/scripts/lib_install/ubuntu-focal/install-all.sh ``` ## Build diff --git a/docs/src/dev-guide/components-core/centos7.4-deps-install.md b/docs/src/dev-guide/components-core/centos-stream-9-deps-install.md similarity index 55% rename from docs/src/dev-guide/components-core/centos7.4-deps-install.md rename to docs/src/dev-guide/components-core/centos-stream-9-deps-install.md index 040bafa42..654b9bf5a 100644 --- a/docs/src/dev-guide/components-core/centos7.4-deps-install.md +++ b/docs/src/dev-guide/components-core/centos-stream-9-deps-install.md @@ -1,4 +1,4 @@ -# Centos 7.4 setup +# Centos Stream 9 setup To install the dependencies required to build clp-core, follow the steps below. These same steps are used by our Docker containers. @@ -18,22 +18,7 @@ without using a packager. So if you ever need to uninstall them, you will need t ::: ```shell -components/core/tools/scripts/lib_install/centos7.4/install-all.sh +components/core/tools/scripts/lib_install/centos-stream-9/install-all.sh ``` -## Set up dependencies - -* Enable gcc 10 - - ```shell - ln -s /opt/rh/devtoolset-10/enable /etc/profile.d/devtoolset.sh - ``` - -* Set PKG_CONFIG_PATH since CentOS doesn't look in `/usr/local` by default. - You should add this to your shell's profile/startup file (e.g., `.bashrc`). - - ```shell - export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig - ``` - -[src-install-script]: https://github.com/y-scope/clp/blob/main/components/core/tools/scripts/lib_install/centos7.4/install-packages-from-source.sh +[src-install-script]: https://github.com/y-scope/clp/blob/main/components/core/tools/scripts/lib_install/centos-stream-9/install-packages-from-source.sh diff --git a/docs/src/dev-guide/components-core/index.md b/docs/src/dev-guide/components-core/index.md index 0e9fd2623..1af0fb13e 100644 --- a/docs/src/dev-guide/components-core/index.md +++ b/docs/src/dev-guide/components-core/index.md @@ -7,17 +7,26 @@ CLP core is the low-level component that performs compression, decompression, an * We have built and tested CLP on the OSes listed [below](#native-environment). * If you have trouble building for another OS, file an issue, and we may be able to help. * A compiler that supports C++20 and std::span (e.g., gcc-10) +* [Task](https://taskfile.dev/) >= 3.38.0 To build, we require some source dependencies, packages from package managers, and libraries built from source. +### Set up + +To initialize the project, run: + +```shell +tools/scripts/deps-download/init.sh +``` + ### Source Dependencies -We use both git submodules and third-party source packages. To download all, you can run this -script: +We use both git submodules and third-party source packages. To download all, run this `task` +command: ```shell -components/core/tools/scripts/deps-download/download-all.sh +task deps:core ``` This will download: @@ -27,6 +36,7 @@ This will download: * [date](https://github.com/HowardHinnant/date.git) (v3.0.1) * [json](https://github.com/nlohmann/json.git) (v3.10.4) * [log-surgeon](https://github.com/y-scope/log-surgeon) (895f464) +* [outcome](https://github.com/ned14/outcome) (v2.2.9) * [simdjson](https://github.com/simdjson/simdjson) (v3.6.3) * [SQLite3](https://www.sqlite.org/download.html) (v3.36.0) * [yaml-cpp](https://github.com/jbeder/yaml-cpp.git) (v0.7.0) @@ -43,8 +53,8 @@ A handful of packages and libraries are required to build CLP. There are two opt See the relevant README for your OS: -* [CentOS 7.4](centos7.4-deps-install) -* [macOS 12](macos12-deps-install) +* [CentOS Stream 9](centos-stream-9-deps-install) +* [macOS](macos-deps-install) * [Ubuntu 20.04](ubuntu-focal-deps-install) * [Ubuntu 22.04](ubuntu-jammy-deps-install) @@ -87,10 +97,11 @@ the relevant paths on your machine. :::{toctree} :hidden: -centos7.4-deps-install -macos12-deps-install +centos-stream-9-deps-install +macos-deps-install ubuntu-focal-deps-install ubuntu-jammy-deps-install +regex-utils ::: [feature-req]: https://github.com/y-scope/clp/issues/new?assignees=&labels=enhancement&template=feature-request.yml diff --git a/docs/src/dev-guide/components-core/macos12-deps-install.md b/docs/src/dev-guide/components-core/macos-deps-install.md similarity index 85% rename from docs/src/dev-guide/components-core/macos12-deps-install.md rename to docs/src/dev-guide/components-core/macos-deps-install.md index f1b8a704b..58483a437 100644 --- a/docs/src/dev-guide/components-core/macos12-deps-install.md +++ b/docs/src/dev-guide/components-core/macos-deps-install.md @@ -1,4 +1,4 @@ -# macOS 12 setup +# macOS setup To install the dependencies required to build clp-core, follow the steps below. These same steps are used by our [GitHub workflow][gh-workflow]. @@ -13,7 +13,7 @@ will not install any dependencies you don't expect. To install all dependencies, run: ```shell -components/core/tools/scripts/lib_install/macos-12/install-all.sh +components/core/tools/scripts/lib_install/macos/install-all.sh ``` [gh-workflow]: https://github.com/y-scope/clp/blob/main/.github/workflows/clp-core-build-macos.yaml diff --git a/docs/src/dev-guide/components-core/regex-utils.md b/docs/src/dev-guide/components-core/regex-utils.md new file mode 100644 index 000000000..b79f0a729 --- /dev/null +++ b/docs/src/dev-guide/components-core/regex-utils.md @@ -0,0 +1,101 @@ +# regex_utils library + +This library contains useful utilities to handle all regex related tasks. + +## Regex to Wildcard Translator + +### Goal + +Performs a best-effort translation to turn a regex string to an equivalent wildcard string. + +CLP currently only recognizes three meta-characters in the wildcard syntax: + +* `?` Matches any single character +* `*` Matches zero or more characters +* `\` Suppresses the special meaning of meta characters (including itself) + +If the regex query can actually be expressed as a wildcard query only deploying the three +metacharacters above, CLP should use the wildcard version. + +### Includes + +* The translator function returns a `Result` type, which can either +contain a value or an error code. + +To use the translator: + +```cpp +#include + +using clp::regex_utils::regex_to_wildcard; + +// Other code + +auto result{regex_to_wildcard(wildcard_str)}; +if (result.has_error()) { + auto err_code{result.error()}; + // Handle error +} else { + auto regex_str{result.value()}; + // Do things with the translated string +} +``` + +* To add custom configuration to the translator: + +```cpp +#include + +RegexToWildcardTranslatorConfig config{true, false, /*...other booleans*/}; +auto result{regex_to_wildcard(wildcard_str, config)}; + +// Same as above +``` + +For a detailed description on the options order and usage, see the +[Custom Configuration](#custom-configuration) section. + +### Functionalities + +* Wildcards + * Turn `.` into `?` + * Turn `.*` into `*` + * Turn `.+` into `?*` + * E.g. `abc.*def.ghi.+` will get translated to `abc*def?ghi?*` +* Metacharacter escape sequences + * An escaped regex metacharacter is treated as a literal and appended to the wildcard output. + * The list of characters that require escaping to have their special meanings suppressed is + `[\/^$.|?*+(){}`. + * Superfluous escape characters are ignored for the following characters: `],<>-_=!`. + * E.g. `a\[\+b\-\_c-_d` will get translated to `a[+b-_c-_d` + * Note: generally, any non-alphanumeric character can be escaped to use it as a literal. The + list this utils library supports is non-exhaustive and can be expanded when necessary. + * For metacharacters shared by both syntaxes, keep the escape backslashes. + * The list of characters that fall into this category is `*?\`. All wildcard metacharacters are + also regex metacharacters. + * E.g. `a\*b\?c\\d` will get translated to `a\*b\?c\\d` (no change) + * Escape sequences with alphanumeric characters are disallowed. + * E.g. Special utility escape sequences `\Q`, `\E`, `\A` etc. and back references `\1` `\2` etc. + cannot be translated. +* Character set + * Reduces a character set into a single character if possible. + * A trivial character set containing a single character or a single escaped metacharacter. + * E.g. `[a]` into `a`, `[\^]` into `^` + * If the `case_insensitive_wildcard` config is turned on, the translator can also reduce the + case-insensitive style character set patterns into a single lowercase character: + * E.g. `[aA]` into `a`, `[Bb]` into `b`, `[xX][Yy][zZ]` into `xyz` + +### Custom configuration + +The `RegexToWildcardTranslatorConfig` class objects are currently immutable once instantiated. By +default, all of the options are set to `false`. + +The constructor takes the following option arguments in order: + +* `case_insensitive_wildcard`: see **Character set** bullet point in the + [Functionalities](#functionalities) section. + +* `add_prefix_suffix_wildcards`: in the absence of regex anchors, add prefix or suffix wildcards so + the query becomes a substring query. + * E.g. `info.*system` gets translated into `*info*system*` which makes the original query a + substring query. diff --git a/docs/src/dev-guide/components-log-viewer-webui.md b/docs/src/dev-guide/components-log-viewer-webui.md new file mode 100644 index 000000000..463d67c78 --- /dev/null +++ b/docs/src/dev-guide/components-log-viewer-webui.md @@ -0,0 +1,86 @@ +# Log Viewer WebUI + +A webapp that allows us to serve the [log-viewer] and integrate it with CLP's [webui]. The webapp +currently consists of a [React] client and a [Fastify] server. + +## Requirements + +* Node.js v20 or higher + +## Setup + +Install the app's dependencies: + +```shell +cd components/log-viewer-webui +(cd client && npm i) +(cd server && npm i) +``` + +## Running + +To run the client during development: + +```shell +npm run start +``` + +To run the server during development: + +```shell +npm run start +``` + +To run the server in production: + +```shell +npm run prod +``` + +In both cases, if you want to customize what host and port the server binds to, you can use the +environment variables in `components/log-viewer-webui/server/.env`. + +## Testing + +To run the server's unit tests: + +```shell +npm test +``` + +## Linting + +You can lint this component either as part of the entire project or as a standalone component. + +### Lint as part of the project + +To check for linting errors: + +```shell +task lint:js-check +``` + +To also fix linting errors (if applicable): + +```shell +task lint:js-fix +``` + +### Lint the component alone + +To check for linting errors: + +```shell +npm run lint:check +``` + +To also fix linting errors (if applicable): + +```shell +npm run lint:fix +``` + +[Fastify]: https://www.fastify.io/ +[log-viewer]: https://github.com/y-scope/yscope-log-viewer +[React]: https://reactjs.org/ +[webui]: components-webui.md diff --git a/docs/src/dev-guide/components-webui.md b/docs/src/dev-guide/components-webui.md index bf2f76f6f..c5a482e12 100644 --- a/docs/src/dev-guide/components-webui.md +++ b/docs/src/dev-guide/components-webui.md @@ -41,7 +41,7 @@ package: ```shell # Please update `` accordingly. - MONGO_URL="mongodb://localhost:27017/clp-search" \ + MONGO_URL="mongodb://localhost:27017/clp-query-results" \ ROOT_URL="http://localhost:4000" \ CLP_DB_USER="clp-user" \ CLP_DB_PASS="" \ diff --git a/docs/src/dev-guide/contributing-linting.md b/docs/src/dev-guide/contributing-linting.md index 599935157..fb246d045 100644 --- a/docs/src/dev-guide/contributing-linting.md +++ b/docs/src/dev-guide/contributing-linting.md @@ -15,7 +15,7 @@ To run the linting tools, besides commonly installed tools like `tar`, you'll ne * `md5sum` * Python 3.8 or newer * python3-venv -* [Task] +* [Task] >= 3.38.0 ## Running the linters diff --git a/docs/src/dev-guide/index.md b/docs/src/dev-guide/index.md index 237cf1df0..1dbf560a0 100644 --- a/docs/src/dev-guide/index.md +++ b/docs/src/dev-guide/index.md @@ -63,6 +63,7 @@ contributing-linting :hidden: components-core/index +components-log-viewer-webui components-webui ::: diff --git a/docs/src/dev-guide/tooling-containers.md b/docs/src/dev-guide/tooling-containers.md index 1b1490e5f..9dc6238f8 100644 --- a/docs/src/dev-guide/tooling-containers.md +++ b/docs/src/dev-guide/tooling-containers.md @@ -3,11 +3,11 @@ We publish (to [GitHub packages][gh-packages]) several Docker container images useful for building and running CLP: -* An [image][core-deps-centos-7.4] containing the dependencies necessary to build CLP core in a - Centos 7.4 x86 environment. +* An [image][core-deps-centos-stream-9] containing the dependencies necessary to build CLP core in a + Centos Stream 9 x86 environment. ```text - ghcr.io/y-scope/clp/clp-core-dependencies-x86-centos7.4:main + ghcr.io/y-scope/clp/clp-core-dependencies-x86-centos-stream-9:main ``` * An [image][core-deps-ubuntu-focal] containing the dependencies necessary to build CLP core in an @@ -45,7 +45,7 @@ and running CLP: ghcr.io/y-scope/clp/clp-execution-x86-ubuntu-jammy:main ``` -[core-deps-centos-7.4]: https://github.com/y-scope/clp/pkgs/container/clp%2Fclp-core-dependencies-x86-centos7.4 +[core-deps-centos-stream-9]: https://github.com/y-scope/clp/pkgs/container/clp%2Fclp-core-dependencies-x86-centos-stream-9 [core-deps-ubuntu-focal]: https://github.com/y-scope/clp/pkgs/container/clp%2Fclp-core-dependencies-x86-ubuntu-focal [core-deps-ubuntu-jammy]: https://github.com/y-scope/clp/pkgs/container/clp%2Fclp-core-dependencies-x86-ubuntu-jammy [core-ubuntu-focal]: https://github.com/y-scope/clp/pkgs/container/clp%2Fclp-core-x86-ubuntu-focal diff --git a/docs/src/dev-guide/tooling-gh-workflows.md b/docs/src/dev-guide/tooling-gh-workflows.md index bd3da8606..ffaec430f 100644 --- a/docs/src/dev-guide/tooling-gh-workflows.md +++ b/docs/src/dev-guide/tooling-gh-workflows.md @@ -28,13 +28,13 @@ shown below. } }%% flowchart LR - filter-relevant-changes --> centos74-deps-image + filter-relevant-changes --> centos-stream-9-deps-image filter-relevant-changes --> ubuntu-focal-deps-image filter-relevant-changes --> ubuntu-jammy-deps-image - filter-relevant-changes --> centos74-binaries + filter-relevant-changes --> centos-stream-9-binaries filter-relevant-changes --> ubuntu-focal-binaries filter-relevant-changes --> ubuntu-jammy-binaries - centos74-deps-image --> centos74-binaries + centos-stream-9-deps-image --> centos-stream-9-binaries ubuntu-focal-deps-image --> ubuntu-focal-binaries ubuntu-jammy-deps-image --> ubuntu-jammy-binaries ubuntu-focal-binaries --> ubuntu-focal-binaries-image @@ -44,14 +44,14 @@ Arrows between jobs indicate a dependency. The jobs are as follows: * `filter-relevant-changes`: Filters the changes in the pull request or commit to determine which of the following jobs should run. -* `centos74-deps-image`: Builds a container image containing the dependencies necessary to build - CLP-core in a CentOS 7.4 x86 environment. +* `centos-stream-9-deps-image`: Builds a container image containing the dependencies necessary to + build CLP-core in a CentOS Stream 9 x86 environment. * `ubuntu-focal-deps-image`: Builds a container image containing the dependencies necessary to build CLP-core in an Ubuntu Focal x86 environment. * `ubuntu-jammy-deps-image`: Builds a container image containing the dependencies necessary to build CLP-core in an Ubuntu Jammy x86 environment. -* `centos74-binaries`: Builds the CLP-core binaries in the built CentOS 7.4 container and runs - core's unit tests. +* `centos-stream-9-binaries`: Builds the CLP-core binaries in the built CentOS Stream 9 container + and runs core's unit tests. * `ubuntu-focal-binaries`: Builds the CLP-core binaries in the built Ubuntu Focal container and runs core's unit tests. * `ubuntu-jammy-binaries`: Builds the CLP-core binaries in the built Ubuntu Jammy container and runs diff --git a/docs/src/user-guide/quick-start-cluster-setup/multi-node.md b/docs/src/user-guide/quick-start-cluster-setup/multi-node.md index aedc6c457..705f2f97a 100644 --- a/docs/src/user-guide/quick-start-cluster-setup/multi-node.md +++ b/docs/src/user-guide/quick-start-cluster-setup/multi-node.md @@ -22,15 +22,15 @@ worker components. The tables below list the components and their functions. :::{table} Controller components :align: left -| Component | Description | -|-----------------------|------------------------------------------------------------------| -| database | Database for archive metadata, compression jobs, and search jobs | -| queue | Task queue for schedulers | -| redis | Task result storage for workers | -| compression_scheduler | Scheduler for compression jobs | -| search_scheduler | Scheduler for search jobs | -| results_cache | Storage for the workers to return search results to the UI | -| webui | Web server for the UI | +| Component | Description | +|-----------------------|-----------------------------------------------------------------| +| database | Database for archive metadata, compression jobs, and query jobs | +| queue | Task queue for schedulers | +| redis | Task result storage for workers | +| compression_scheduler | Scheduler for compression jobs | +| query_scheduler | Scheduler for search/aggregation jobs | +| results_cache | Storage for the workers to return search results to the UI | +| webui | Web server for the UI | ::: :::{table} Worker components @@ -39,12 +39,12 @@ worker components. The tables below list the components and their functions. | Component | Description | |--------------------|--------------------------------------------------------------| | compression_worker | Worker processes for compression jobs | -| search_worker | Worker processes for search/aggregation jobs | +| query_worker | Worker processes for search/aggregation jobs | | reducer | Reducers for performing the final stages of aggregation jobs | ::: :::{note} -Running additional workers increases the parallelism of compression and search jobs. +Running additional workers increases the parallelism of compression and search/aggregation jobs. ::: ## Configuring CLP @@ -92,12 +92,12 @@ but all components in a group must be started before starting a component in the **Group 2 components:** * `compression_scheduler` -* `search_scheduler` +* `query_scheduler` **Group 3 components:** * `compression_worker` -* `search_worker` +* `query_worker` * `reducer` For each component, on the host where you want to run the component, run: diff --git a/docs/tasks.yml b/docs/tasks.yml index 05ec8aeef..45e2cf05e 100644 --- a/docs/tasks.yml +++ b/docs/tasks.yml @@ -25,7 +25,7 @@ tasks: dir: "{{.TASKFILE_DIR}}" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -34,16 +34,20 @@ tasks: # Call `clean` before building since `sphinx-build --write-all --fresh-env` isn't always # equivalent to building from scratch. - task: "clean" + - "python3 '{{.ROOT_DIR}}/tools/scripts/find-broken-docs-links.py'" - |- . "{{.G_DOCS_VENV_DIR}}/bin/activate" sphinx-build \ --write-all \ --fresh-env \ --conf-dir conf \ + --nitpicky \ + --fail-on-warning \ + --keep-going \ --builder html \ src "{{.OUTPUT_DIR}}" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" @@ -63,18 +67,18 @@ tasks: REQUIREMENTS_FILE: "docs/requirements.txt" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: ":create-venv" + - task: ":utils:create-venv" vars: LABEL: "docs" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "{{.REQUIREMENTS_FILE}}" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" @@ -91,7 +95,7 @@ tasks: OUTPUT_DIR: "{{.G_NODE_DEPS_DIR}}" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -99,7 +103,7 @@ tasks: - "rm -rf '{{.OUTPUT_DIR}}'" - "npm --prefix '{{.OUTPUT_DIR}}' install http-server" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" diff --git a/lint-requirements.txt b/lint-requirements.txt index 0c58ad2af..4b3bbe927 100644 --- a/lint-requirements.txt +++ b/lint-requirements.txt @@ -1,4 +1,5 @@ black>=24.4.2 -clang-format>=18.1.5 +# Lock to v18.x until we can upgrade our code to meet v19's formatting standards. +clang-format~=18.1 ruff>=0.4.4 yamllint>=1.35.1 diff --git a/lint-tasks.yml b/lint-tasks.yml index ba5c30fbf..d1da57771 100644 --- a/lint-tasks.yml +++ b/lint-tasks.yml @@ -1,9 +1,8 @@ version: "3" vars: - G_LINTER_NODEJS_BUILD_DIR: "{{.G_BUILD_DIR}}/linter-nodejs" - G_LINTER_NODEJS_BIN_DIR: "{{.G_LINTER_NODEJS_BUILD_DIR}}/bin" G_LINT_VENV_DIR: "{{.G_BUILD_DIR}}/lint-venv" + G_WEBUI_SRC_DIR: "{{.ROOT_DIR}}/components/webui" tasks: check: @@ -21,12 +20,9 @@ tasks: - task: "yml-fix" cpp-check: - dir: "components/core" - cmds: - - task: "cpp" - vars: - FLAGS: "--dry-run" sources: &cpp_source_files + - "{{.ROOT_DIR}}/.clang-format" + - "{{.ROOT_DIR}}/.clang-tidy" - "{{.TASKFILE}}" - ".clang-format" - "src/**/*.cpp" @@ -37,44 +33,55 @@ tasks: - "tests/**/*.h" - "tests/**/*.hpp" - "tests/**/*.inc" + dir: "components/core" + cmds: + - task: "cpp" + vars: + FLAGS: "--dry-run" cpp-fix: + sources: *cpp_source_files dir: "components/core" cmds: - task: "cpp" vars: FLAGS: "-i" - sources: *cpp_source_files js-check: - dir: "components/webui" - cmds: - - task: "js" - vars: - LINT_CMD: "check" sources: &js_source_files - "{{.G_BUILD_DIR}}/lint#linter-node-modules.md5" + - "{{.G_BUILD_DIR}}/log-viewer-webui-node-modules.md5" - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/**/*.css" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/**/*.jsx" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/webpack.config.js" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/settings.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/src/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/client/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/client/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/imports/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/imports/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/launcher.js" + - "{{.G_WEBUI_SRC_DIR}}/package.json" + - "{{.G_WEBUI_SRC_DIR}}/server/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/server/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/tests/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/tests/**/*.jsx" - "{{.ROOT_DIR}}/Taskfile.yml" - "{{.TASKFILE}}" - - "client/**/*.js" - - "client/**/*.jsx" - - "imports/**/*.js" - - "imports/**/*.jsx" - - "launcher.js" - - "package.json" - - "server/**/*.js" - - "server/**/*.jsx" - - "tests/**/*.js" - - "tests/**/*.jsx" + cmds: + - task: "js" + vars: + LINT_CMD: "check" js-fix: - dir: "components/webui" + sources: *js_source_files cmds: - task: "js" vars: LINT_CMD: "fix" - sources: *js_source_files py-check: cmds: @@ -105,6 +112,7 @@ tasks: components/core/config \ components/package-template/src/etc \ docs \ + deps-tasks.yml \ lint-tasks.yml \ Taskfile.yml @@ -112,8 +120,8 @@ tasks: internal: true requires: vars: ["FLAGS"] - deps: ["venv"] dir: "components/core" + deps: ["cpp-lint-configs", "venv"] cmds: - |- . "{{.G_LINT_VENV_DIR}}/bin/activate" @@ -123,14 +131,22 @@ tasks: -print0 | \ xargs -0 clang-format {{.FLAGS}} -Werror + cpp-lint-configs: + internal: true + cmd: "{{.ROOT_DIR}}/tools/yscope-dev-utils/lint-configs/symlink-cpp-lint-configs.sh" + js: internal: true requires: vars: ["LINT_CMD"] - deps: ["linter-node-modules"] - dir: "components/webui" + deps: [":log-viewer-webui-node-modules", "linter-node-modules"] cmds: - - "PATH='{{.G_LINTER_NODEJS_BIN_DIR}}':$PATH npm run 'lint:{{.LINT_CMD}}'" + - for: + - "components/log-viewer-webui" + - "components/webui" + cmd: |- + cd "{{.ITEM}}" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm run "lint:{{.LINT_CMD}}" py: internal: true @@ -141,7 +157,9 @@ tasks: - for: - "components/clp-package-utils/clp_package_utils" - "components/clp-py-utils/clp_py_utils" + - "components/core/tools/scripts/utils" - "components/job-orchestration/job_orchestration" + - "tools/scripts" - "docs/conf" cmd: |- . "{{.G_LINT_VENV_DIR}}/bin/activate" @@ -149,73 +167,61 @@ tasks: black --color --line-length 100 {{.BLACK_FLAGS}} . ruff check {{.RUFF_FLAGS}} . - linter-nodejs: + linter-node-modules: internal: true vars: + WEBUI_LINTER_DIR: "{{.ROOT_DIR}}/components/webui/linter" + OUTPUT_DIR: "{{.WEBUI_LINTER_DIR}}/node_modules" CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" - OUTPUT_DIR: "{{.G_LINTER_NODEJS_BUILD_DIR}}" - cmds: - - task: ":nodejs" - vars: - CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" - NODEJS_VERSION: "latest" - OUTPUT_DIR: "{{.OUTPUT_DIR}}" - - linter-node-modules: - internal: true + sources: + - "{{.G_BUILD_DIR}}/nodejs-22.md5" + - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + - "../package.json" + dir: "{{.WEBUI_LINTER_DIR}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - ":init" - ":webui-node-modules" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - - "linter-nodejs" - dir: "{{.WEBUI_LINTER_DIR}}" - vars: - WEBUI_LINTER_DIR: "{{.ROOT_DIR}}/components/webui/linter" - OUTPUT_DIR: "{{.WEBUI_LINTER_DIR}}/node_modules" - CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" + - ":nodejs-22" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - - "PATH='{{.G_LINTER_NODEJS_BIN_DIR}}':$PATH npm update" + - "PATH='{{.G_NODEJS_22_BIN_DIR}}':$PATH npm update" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/lint#linter-nodejs.md5" - - "{{.G_BUILD_DIR}}/webui-node-modules.md5" - - "{{.ROOT_DIR}}/Taskfile.yml" - - "{{.TASKFILE}}" - - "../package.json" - generates: ["{{.CHECKSUM_FILE}}"] venv: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" OUTPUT_DIR: "{{.G_LINT_VENV_DIR}}" + sources: + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + - "lint-requirements.txt" + generates: ["{{.CHECKSUM_FILE}}"] deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: ":create-venv" + - task: ":utils:create-venv" vars: LABEL: "lint" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "lint-requirements.txt" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/Taskfile.yml" - - "{{.TASKFILE}}" - - "lint-requirements.txt" - generates: ["{{.CHECKSUM_FILE}}"] diff --git a/requirements.txt b/requirements.txt index d48e14ebb..fdd8fafea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pip~=23.3 poetry~=1.7 -setuptools~=69.0 +setuptools~=75.2.0 diff --git a/tools/scripts/deps-download/download-dep.py b/tools/scripts/deps-download/download-dep.py new file mode 100644 index 000000000..5866461fd --- /dev/null +++ b/tools/scripts/deps-download/download-dep.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import argparse +import logging +import os +import shutil +import subprocess +import sys +import tempfile +import urllib.parse +import urllib.request +from pathlib import Path + +# Setup logging +# Create logger +logger = logging.getLogger() +logger.setLevel(logging.INFO) +# Setup console logging +logging_console_handler = logging.StreamHandler() +logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") +logging_console_handler.setFormatter(logging_formatter) +logger.addHandler(logging_console_handler) + + +def main(argv): + args_parser = argparse.ArgumentParser(description="Download dependency.") + args_parser.add_argument("source_url", help="URL of the source file.") + args_parser.add_argument("source_name", help="Name of the source file.") + args_parser.add_argument("dest_dir", help="Output directory for the download.") + args_parser.add_argument("--extract", action="store_true", help="Extract the source file.") + args_parser.add_argument( + "--no-submodule", + action="store_false", + dest="use_submodule", + help="Don't use git submodule update", + ) + + parsed_args = args_parser.parse_args(argv[1:]) + source_url = parsed_args.source_url + source_name = parsed_args.source_name + dest_dir = Path(parsed_args.dest_dir).resolve() + extract_source = parsed_args.extract + + script_path = Path(os.path.realpath(__file__)) + git_dir = script_path.parent / ".." / ".." / ".." / ".git" + if git_dir.exists() and git_dir.is_dir(): + if parsed_args.use_submodule: + cmd = ["git", "submodule", "update", "--init", "--recursive", str(dest_dir)] + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError: + logger.exception(f"Failed to update submodule '{dest_dir}'.") + return -1 + return 0 + + with tempfile.TemporaryDirectory() as work_dir_name: + work_dir = Path(work_dir_name) + + parsed_url = urllib.parse.urlparse(source_url) + filename = Path(parsed_url.path).name + file_path = work_dir / filename + + # Download file + urllib.request.urlretrieve(source_url, file_path) + if extract_source: + # NOTE: We need to convert `file_path` to a str since `unpack_archive` only accepts a + # path-like object on Python versions >= 3.7 + shutil.unpack_archive(str(file_path), work_dir) + + if dest_dir.exists(): + shutil.rmtree(dest_dir, ignore_errors=True) + else: + dest_dir.parent.mkdir(parents=True, exist_ok=True) + + source_path = work_dir / source_name + if not source_path.exists(): + logger.error(f"Source '{source_path}' doesn't exist.") + return -1 + + if extract_source: + shutil.copytree(source_path, dest_dir) + else: + shutil.copy(source_path, dest_dir) + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/tools/scripts/deps-download/init.sh b/tools/scripts/deps-download/init.sh new file mode 100755 index 000000000..fa626cc82 --- /dev/null +++ b/tools/scripts/deps-download/init.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Exit on any error +set -e + +# Error on undefined variable +set -u + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +project_root_dir="$script_dir/../../../" +download_dep_script="$script_dir/download-dep.py" + +python3 "${download_dep_script}" \ + https://github.com/y-scope/yscope-dev-utils/archive/2caa3dcf.zip \ + yscope-dev-utils-2caa3dcfbbccff052d179e643a509d8ad05bc217 \ + "${project_root_dir}/tools/yscope-dev-utils" \ + --extract diff --git a/tools/scripts/find-broken-docs-links.py b/tools/scripts/find-broken-docs-links.py new file mode 100644 index 000000000..1a1eb7427 --- /dev/null +++ b/tools/scripts/find-broken-docs-links.py @@ -0,0 +1,105 @@ +import os +import subprocess +import sys +from pathlib import Path + + +def main(argv): + repo_root = _get_repo_root() + + found_violation = False + + # Check for docs.yscope.com links with ".md" suffixes + if _check_tracked_files( + r"docs\.yscope\.com/.+\.md", + repo_root, + repo_root, + 'docs.yscope.com links cannot have ".md" suffixes.', + ): + found_violation = True + + # Check for sphinx :link: attributes that have ".md" suffixes + if _check_tracked_files( + r":link:[[:space:]]*.+\.md", + repo_root, + repo_root / "docs", + 'sphinx :link: attributes cannot have ".md" suffixes', + ): + found_violation = True + + if found_violation: + return 1 + + return 0 + + +def _get_repo_root() -> Path: + path_str = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], cwd=Path(__file__).parent, text=True + ) + return Path(path_str.strip()) + + +def _check_tracked_files( + pattern: str, repo_root: Path, dir_to_search: Path, error_msg: str +) -> bool: + """ + Check for a pattern in all tracked files in the repo (except this script). + :param pattern: The pattern to search for. + :param repo_root: The root of the repository. + :param dir_to_search: The directory to search in. + :param error_msg: Error message if the pattern is found. + :return: Whether the pattern was found in any file. + """ + found_matches = False + + # NOTE: "-z" ensures the paths won't be quoted (while delimiting them using '\0') + for path_str in subprocess.check_output( + [ + "git", + "ls-files", + "--cached", + "--exclude-standard", + "-z", + str(dir_to_search.relative_to(repo_root)), + ], + cwd=repo_root, + text=True, + ).split("\0"): + path = Path(path_str) + + # Skip directories and this script + if path == __file__ or (repo_root / path).is_dir(): + continue + + try: + for match in subprocess.check_output( + ["grep", "--extended-regexp", "--line-number", "--with-filename", pattern, path], + cwd=repo_root, + text=True, + ).splitlines(): + _parse_and_print_match(match, error_msg) + found_matches = True + except subprocess.CalledProcessError: + pass + + return found_matches + + +def _parse_and_print_match(match: str, error_msg: str): + """ + Parses and prints grep matches in a format relevant to the current environment. + :param match: The match to parse and print. + :param error_msg: Error message if the pattern is found. + """ + if os.getenv("GITHUB_ACTIONS") == "true": + # Print a GitHub Actions error annotation + file, line, _ = match.split(":", 2) + print(f"::error file={file},line={line}::{error_msg}") + else: + print(error_msg, file=sys.stderr) + print(match, file=sys.stderr) + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/tools/yscope-dev-utils b/tools/yscope-dev-utils new file mode 160000 index 000000000..ad576e43c --- /dev/null +++ b/tools/yscope-dev-utils @@ -0,0 +1 @@ +Subproject commit ad576e43c1a43d7a6afde79fc9c3c952b7bf28bd